C/C++ Windows Socket (Winsock) TCP Socket Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 C/C++ Windows Socket TCP Socket Server/Client 網路通訊程式,Windows Socket 簡稱 Winsock,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中又以 TCP 通訊最常被使用,TCP 通訊程式通常分成伺服器端與客戶端的兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 TCP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 這樣的通訊程式。

以下 C/C++ Winsock TCP 內容將分為幾部分,分別為:

  • 常見的 Socket API 函式 Overview 總覽
  • C/C++ Winsock Socket TCP Server/Client 通訊流程
  • C/C++ Winsock TCP Server 伺服器端程式 (Echo Sever)
  • C/C++ Winsock TCP Client 客戶端程式 (傳送使用者的輸入)
  • C/C++ Winsock TCP Client 客戶端程式 (定時傳送資料)
  • C/C++ Winsock TCP 常見問題
  • Winsock 的 ip 字串轉 sockaddr_in
  • Winsock 的 sockaddr_in 轉 ip 字串

常見的 Socket API 函式 Overview 總覽

C/C++ Winsock 的 socket 模組它提供了標準的 BSD Socket API,主要的 socket API 函式如下:
socket():建立 socket 與設定使用哪種通訊協定
bind(sock_fd, addr):將 socket 綁定到地址
listen(sock_fd, n):開始監聽 TCP 傳入連接,n 指定在拒絕連線前,操作系統可以掛起的最大連接數,該值最少為1,通常設為5就夠用了
accept(sock_fd, addr):等待連線,接受到 TCP 連線後,可以從 addr 得知連線客戶端的地址。
connect(address):連線到 address 處的 socket
recv():接收 TCP 資料
send():發送 TCP 資料
closesocket():關閉 socket

C/C++ Winsock Socket TCP Server/Client 通訊流程

以下 ShengYu 講解 C/C++ Winsock TCP Server 端與 TCP Client 端的程式流程以及會如何使用這些 socket API,
TCP Server 的流程分為以下幾大步驟:

  1. 建立socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);,指定 AF_INET (Internet Protocol) family 的通訊協定,類型使用 SOCK_STREAM (Stream Socket) 也就是 TCP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:bind(sock_fd, ...)
  3. 開始監聽:listen(sock_fd, ...)
  4. 等待與接受客戶端的請求連線:new_fd = accept(sock_fd, ...)
  5. 接收客戶端傳來的資料:recv(new_fd, ...)
  6. 傳送給對方發送資料:send(new_fd, ...)
  7. 傳輸完畢後,關閉 socket:closesocket(new_fd)

TCP Client 的流程分為以下幾大步驟:

  1. 建立 socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  2. 連線至遠端地址:connect(sock_fd, ...)
  3. 傳送資料:send(sock_fd, ...)
  4. 接收資料:recv(sock_fd, ...)
  5. 傳輸完畢後,關閉 socket:closesocket(sock_fd)

以上是 TCP Server/Client 通訊的重點流程,實際的 C/C++ Winsock socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 TCP Server/Client 通訊程式吧!

C/C++ Winsock TCP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 C/C++ Winsock TCP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

要使用 Winsock 的話要 include winsock2.h 標頭檔,winsock2.h 是用來取代 winsock.h,除非你要使用 Winsock 1.1 否則你應該使用 winsock2.h 標頭檔,winsock.h 要連結的函式庫為 wsock32.lib,而 winsock2.h 要連結為 Ws2_32.lib 函式庫。

另外一點要注意的是 windows.h 預設會 include winsock.h,所以如果你在 windows.h 後面在 include winsock2.h 的話會編譯錯誤,原因是因為 winsock.h 跟 winsock2.h 這兩個不應該同時存在,winsock2.h 的設計是用來取代 winsock.h 的而不是擴充的概念,所以正確做法會是 winsock2.h 要在 windows.h 之前被 include。

跟 linux socket 相比,winsock 需要在最一開始要呼叫 WSAStartup 函式,以及最後要呼叫 WSACleanup 函式。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,
一旦有客戶端連線的話,就會從 accept() 繼續往下執行,
之後是另一個迴圈來服務這個連線,不斷地從這個連線 recv 接收資料與 send 傳送資料,
如果 recv() 的回傳值為0,表示客戶端已斷開連線,此時我們也關閉這個連線,
之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 C/C++ Winsock TCP 伺服器程式。

cpp-windows-tcp-socket-server.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
SOCKET sock, new_sock;
socklen_t addrlen;
struct sockaddr_in my_addr, client_addr;
int status;
char indata[1024] = {0}, outdata[1024] = {0};
int on = 1;

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET) {
perror("Socket creation error");
exit(1);
}

// for "Address already in use" error message
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}

// server address
my_addr.sin_family = AF_INET;
inet_pton(AF_INET, host, &my_addr.sin_addr);
my_addr.sin_port = htons(port);

status = bind(sock, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (status == -1) {
perror("Binding error");
exit(1);
}
char my_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &my_addr.sin_addr, my_ip, sizeof(my_ip));
printf("server start at: %s:%d\n", my_ip, port);

status = listen(sock, 5);
if (status == -1) {
perror("Listening error");
exit(1);
}
printf("wait for connection...\n");

addrlen = sizeof(client_addr);

while (1) {
new_sock = accept(sock, (struct sockaddr *)&client_addr, &addrlen);
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
printf("connected by %s:%d\n", client_ip, ntohs(client_addr.sin_port));

while (1) {
int nbytes = recv(new_sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(new_sock);
printf("client closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sprintf(outdata, "echo %s", indata);
send(new_sock, outdata, strlen(outdata), 0);
}
}
closesocket(sock);
WSACleanup();

return 0;
}

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 Binding error: Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
2
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(int));

C/C++ Winsock TCP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 TCP 伺服器端的程式,接著再用另一個終端機執行 TCP 客戶端的程式。C/C++ Winsock TCP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,之後 connect() 連線伺服器主機的 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,這邊是使用 gets_s() 取得使用者輸入的資料,也可以使用 scanf()fgets() 等函式,
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

cpp-windows-tcp-socket-client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "127.0.0.1";
int port = 7000;

int main()
{
SOCKET sock;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_pton(AF_INET, host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
printf("please input message: ");
gets_s(outdata);
printf("send: %s\n", outdata);
send(sock, outdata, strlen(outdata), 0);

int nbytes = recv(sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(sock);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);
}
WSACleanup();

return 0;
}

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
> client.exe
please input message: hello
send: hello
recv: echo hello
please input message: hello tcp
send: hello tcp
recv: echo hello tcp
please input message: send:
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
> server.exe
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:25886
recv: hello
recv: hello tcp
client closed connection.

C/C++ Winsock TCP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 TCP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 TCP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 Sleep(1000) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

cpp-windows-tcp-socket-client-heartbeat.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#define _CRT_SECURE_NO_WARNINGS
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// Link with ws2_32.lib
#pragma comment(lib, "Ws2_32.lib")

const char* host = "127.0.0.1";
int port = 7000;

int main()
{
SOCKET sock;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// init winsock
WSADATA wsa = { 0 };
WORD wVer = MAKEWORD(2, 2);
WSAStartup(wVer, &wsa);
if (WSAStartup(MAKEWORD(2, 2), &wsa) != NO_ERROR) {
printf("Error: init winsock\n");
exit(1);
}

// create a socket
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_pton(AF_INET, host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
strcpy(outdata, "heartbeat");
printf("send: %s\n", outdata);
send(sock, outdata, strlen(outdata), 0);

int nbytes = recv(sock, indata, sizeof(indata), 0);
if (nbytes <= 0) {
closesocket(sock);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);

Sleep(1000);
}
WSACleanup();

return 0;
}

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
> client.exe
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
9
10
> server.exe
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:25910
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
client closed connection.

C/C++ Winsock TCP 常見問題

在 TCP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recv 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recv 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recv 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recv 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

Winsock 的 ip 字串轉 sockaddr_in

在 linux 中 ip 字串轉換成 sockaddr_in 結構可用 inet_aton (ipv4 only)跟 inet_pton (ipv4 & ipv6) 兩個 API 達成,但在 windows 中沒有 inet_aton,而是 inet_addr (ipv4) 可用,
inet_addr 用法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
my_addr.sin_addr.s_addr = inet_addr(host);

但現代的 MS 編譯器都出現 C4996 編譯錯誤,內容如下,

1
'inet_addr': Use inet_pton() or InetPton() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings

除非你要使用 define _WINSOCK_DEPRECATED_NO_WARNINGS,否則我建議改使用 inet_pton,使用 inet_pton 的話好處就是 windows 跟 linux 都可以通用,跨平台時程式碼修改的比較少,所以基本上在 windows 平台中使用 inet_pton 就對了!
inet_pton 使用方法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_pton(AF_INET, host, &my_addr.sin_addr); // AF_INET -> ipv4

Winsock 的 sockaddr_in 轉 ip 字串

承上節,在 Winsock 中 要將 sockaddr_in 結構轉換成 ip 字串的話可用 inet_ntop (ipv4 & ipv6) API 達成,
inet_ntop 用法如下,

1
2
3
struct sockaddr_in client_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));

以上就是 C/C++ Windows Socket (Winsock) TCP Socket Server/Client 網路通訊教學,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

Linux su 切換使用者指令用法與範例

本篇介紹 Linux su 指令用法與範例,su 指令可以用來切換當前使用者到其他使用者,su 切換使用者時需輸入要切換的帳號跟密碼,切換使用者後可以測試該使用者的執行權限。

su 後面接使用者名稱,範例如下,

1
2
# 切換成 root
su root

切換成某 user,

1
su <user_name>

su -c <指令>:執行完指定的指令後,就恢復原來的使用者。

su 執行檔路徑在哪

想知道 su 執行檔路徑在哪的話,可以透過 which su 查詢得知,通常在 在 /bin/ 下,macOS 則是在 /usr/bin/su
如果想要知道目前的使用者是誰的話可以使用 whoami,

以上就是 Linux su 切換使用者指令範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包

C/C++ Linux TCP Socket Server/Client 網路通訊教學

本篇 ShengYu 介紹如何寫 C/C++ Linux TCP Socket Server/Client 網路通訊程式,在這個網路盛行的時代,網路通訊已成為基礎,想要精通學習網路通訊必須先了解 TCP/IP 協定,其中又以 TCP 通訊最常被使用,TCP 通訊程式通常分成伺服器端與客戶端的兩部份程式,接下來教學內容將介紹如何使用 socket API 來搭建一個典型的 TCP 通訊程式,甚至可以寫出一個聊天室的程式,或者像 LINE 這樣的通訊程式。

以下 C/C++ Linux TCP 內容將分為幾部分,分別為:

  • 常見的 Socket API 函式 Overview 總覽
  • C/C++ Linux Socket TCP Server/Client 通訊流程
  • C/C++ Linux TCP Server 伺服器端程式 (Echo Sever)
  • C/C++ Linux TCP Client 客戶端程式 (傳送使用者的輸入)
  • C/C++ Linux TCP Client 客戶端程式 (定時傳送資料)
  • C/C++ Linux TCP 常見問題
  • Linux sokcet 的 ip 字串轉 sockaddr_in
  • Linux sokcet 的 sockaddr_in 轉 ip 字串

常見的 Socket API 函式 Overview 總覽

C/C++ Linux 的 socket 模組它提供了標準的 BSD Socket API,主要的 socket API 函式如下:
socket():建立 socket 與設定使用哪種通訊協定
bind(sock_fd, addr):將 socket 綁定到地址
listen(sock_fd, n):開始監聽 TCP 傳入連接,n 指定在拒絕連線前,操作系統可以掛起的最大連接數,該值最少為1,通常設為5就夠用了
accept(sock_fd, addr):等待連線,接受到 TCP 連線後,可以從 addr 得知連線客戶端的地址。
connect(address):連線到 address 處的 socket
recv():接收 TCP 資料
send():發送 TCP 資料
close():關閉 socket

C/C++ Linux Socket TCP Server/Client 通訊流程

以下 ShengYu 講解 C/C++ Linux TCP Server 端與 TCP Client 端的程式流程以及會如何使用這些 socket API,
TCP Server 的流程分為以下幾大步驟:

  1. 建立socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);,指定 AF_INET (Internet Protocol) family 的通訊協定,類型使用 SOCK_STREAM (Stream Socket) 也就是 TCP 傳輸方式
  2. 綁定 socket 到本地 IP 與 port:bind(sock_fd, ...)
  3. 開始監聽:listen(sock_fd, ...)
  4. 等待與接受客戶端的請求連線:new_fd = accept(sock_fd, ...)
  5. 接收客戶端傳來的資料:recv(new_fd, ...)
  6. 傳送給對方發送資料:send(new_fd, ...)
  7. 傳輸完畢後,關閉 socket:close(new_fd)

TCP Client 的流程分為以下幾大步驟:

  1. 建立 socket:sock_fd = socket(AF_INET, SOCK_STREAM, 0);
  2. 連線至遠端地址:connect(sock_fd, ...)
  3. 傳送資料:send(sock_fd, ...)
  4. 接收資料:recv(sock_fd, ...)
  5. 傳輸完畢後,關閉 socket:close(sock_fd)

以上是 TCP Server/Client 通訊的重點流程,實際的 C/C++ Linux socket API 用法與範例詳見下列章節,接下來就來看看怎麼寫 TCP Server/Client 通訊程式吧!

C/C++ Linux TCP Server 伺服器端程式 (Echo Sever)

這邊 ShengYu 就開始介紹怎麼寫 C/C++ Linux TCP Server 程式,下列範例這是一個典型的 Echo Server,Echo Server 就是收到什麼資料就回覆什麼資料,很簡單吧!
跟網路上其他範例不同的是此範例建立連線後不是傳輸一次資料就關閉連線,而是使用迴圈可以一直傳輸資料直到客戶端不想傳關閉連線為止,並且伺服器端再次地等待新的客戶端連線來服務。

如下例所示,伺服器端一開始建立 socket,用 bind() 綁定,這裡是使用 0.0.0.0, port 為 7000
使用 listen() 開始監聽,上限連線數為5,之後進入主迴圈,accept() 等待接受客戶端的連線請求,
一旦有客戶端連線的話,就會從 accept() 繼續往下執行,
之後是另一個迴圈來服務這個連線,不斷地從這個連線 recv 接收資料與 send 傳送資料,
如果 recv() 的回傳值為0,表示客戶端已斷開連線,此時我們也關閉這個連線,
之後回到 accept() 等待新的客戶端連線,等到新的客戶端連線連上便跟之前的流程一樣,這樣便是一個完整的 C/C++ Linux TCP 伺服器程式。

cpp-linux-tcp-socket-server.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// g++ cpp-linux-tcp-socket-server.cpp -o server
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd, new_fd;
socklen_t addrlen;
struct sockaddr_in my_addr, client_addr;
int status;
char indata[1024] = {0}, outdata[1024] = {0};
int on = 1;

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// for "Address already in use" error message
if (setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int)) == -1) {
perror("Setsockopt error");
exit(1);
}

// server address
my_addr.sin_family = AF_INET;
inet_aton(host, &my_addr.sin_addr);
my_addr.sin_port = htons(port);

status = bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(my_addr));
if (status == -1) {
perror("Binding error");
exit(1);
}
printf("server start at: %s:%d\n", inet_ntoa(my_addr.sin_addr), port);

status = listen(sock_fd, 5);
if (status == -1) {
perror("Listening error");
exit(1);
}
printf("wait for connection...\n");

addrlen = sizeof(client_addr);

while (1) {
new_fd = accept(sock_fd, (struct sockaddr *)&client_addr, &addrlen);
printf("connected by %s:%d\n", inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port));

while (1) {
int nbytes = recv(new_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(new_fd);
printf("client closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sprintf(outdata, "echo %s", indata);
send(new_fd, outdata, strlen(outdata), 0);
}
}
close(sock_fd);

return 0;
}

如果 Server 伺服器端不正常關閉後再次啟動時可能會遇到 Binding error: Address already in use 這種錯誤訊息的話,那麼你可以在 bind() 之前設定 REUSEADDR 可以解決這個問題,

1
2
int on = 1;
setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int));

C/C++ Linux TCP Client 客戶端程式 (傳送使用者的輸入)

先用一個終端機來啟動前述的 TCP 伺服器端的程式,接著再用另一個終端機執行 TCP 客戶端的程式。C/C++ Linux TCP Client 範例如下,這邊要示範的是傳送使用者的輸入訊息,將使用者的輸入訊息傳送給伺服器端,通常應用於一般聊天軟體上,學習之後就可以寫一個簡單的聊天軟體了。

如下例所示,客戶端一開始建立 socket,之後 connect() 連線伺服器主機的 host 與 port,
之後進入主迴圈,不斷地傳送使用者的輸入,這邊是使用 gets() 取得使用者輸入的資料,也可以使用 scanf()fgets() 等函式,
使用者輸入完後按下 Enter 便會將資料發送給伺服器端,接著等待伺服器端傳送資料,接收到來自伺服器端的資料就把它印出來,

cpp-linux-tcp-socket-client.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
// g++ cpp-linux-tcp-socket-client.cpp -o client
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_aton(host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock_fd, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
printf("please input message: ");
gets(outdata);
printf("send: %s\n", outdata);
send(sock_fd, outdata, strlen(outdata), 0);

int nbytes = recv(sock_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(sock_fd);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);
}

return 0;
}

以下示範一下程式的啟動過程,過程中我在客戶端輸入了兩次的訊息,最後按 ctrl+c 結束了程式,
客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
$ ./client
please input message: hello
send: hello
recv: echo hello
please input message: hello tcp
send: hello tcp
recv: echo hello tcp
please input message: ^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
$ ./server
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:59362
recv: hello
recv: hello tcp
client closed connection.

C/C++ Linux TCP Client 客戶端程式 (定時傳送資料)

前一章節示範了 Echo Sever 與 Client 通訊程式,這時可以打鐵趁熱,除了前一章節 TCP Client 使用者手動輸入的情形之外,這邊也介紹另一種客戶端會定時地傳送資料給伺服器端,同時這也適用於各種通訊情形。

步驟跟前一章節 TCP Client 幾乎相同,傳輸字串為 'heartbeat',這邊傳送後使用 sleep(1) 來讓程式睡眠1秒,之後再繼續傳送資料,進而達成定時傳送的功能,

cpp-linux-tcp-socket-client-heartbeat.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// g++ cpp-linux-tcp-socket-client-heartbeat.cpp -o client
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* host = "0.0.0.0";
int port = 7000;

int main()
{
int sock_fd;
struct sockaddr_in serv_name;
int status;
char indata[1024] = {0}, outdata[1024] = {0};

// create a socket
sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1) {
perror("Socket creation error");
exit(1);
}

// server address
serv_name.sin_family = AF_INET;
inet_aton(host, &serv_name.sin_addr);
serv_name.sin_port = htons(port);

status = connect(sock_fd, (struct sockaddr *)&serv_name, sizeof(serv_name));
if (status == -1) {
perror("Connection error");
exit(1);
}

while (1) {
strcpy(outdata, "heartbeat");
printf("send: %s\n", outdata);
send(sock_fd, outdata, strlen(outdata), 0);

int nbytes = recv(sock_fd, indata, sizeof(indata), 0);
if (nbytes <= 0) {
close(sock_fd);
printf("server closed connection.\n");
break;
}
printf("recv: %s\n", indata);

sleep(1);
}

return 0;
}

客戶端輸出結果如下,

client
1
2
3
4
5
6
7
8
9
10
11
12
$ ./client
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
send: heartbeat
recv: echo heartbeat
^C

伺服器端輸出結果如下,

server
1
2
3
4
5
6
7
8
9
10
$ ./server
server start at: 0.0.0.0:7000
wait for connection...
connected by 127.0.0.1:33862
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
recv: heartbeat
client closed connection.

C/C++ Linux TCP 常見問題

在 TCP 的傳輸裡,為什麼伺服器還要回傳給客戶端?
因為這只是個示範用的通訊程式,讓你了解通訊的過程,就像打電話或者跟別人對話一樣,你一句我一句的來回互動,你可以根據實際的需求而修改程式,你也可以改成一直傳,例如客戶端一直傳送,伺服器一直接收。

為什麼 recv 還沒收到資料前會卡住一直等?
因為預設是 blocking 非阻塞模式,recv 還沒收到資料前會卡住一直等,沒法做其他事情,直到 recv 接收到資料才會從 recv 函式返回,解決辦法是改用 Non-blocking 非阻塞模式,Non-blocking 模式是這次沒接收到資料就會從 recv 函式返回,接著繼續往下執行;另一個解決方式是另外建立執行緒去做其他事情。

Linux sokcet 的 ip 字串轉 sockaddr_in

在 Linux 中 ip 字串轉換成 sockaddr_in 結構可用 inet_aton (ipv4 only)跟 inet_pton (ipv4 & ipv6) 兩個 API 達成,但在 windows 中沒有 inet_aton,而是 inet_addr (ipv4) 可用,所以用 inet_aton 的缺點是跨平台的話還要在修改這部分的程式碼,

inet_aton 用法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_aton(host, &my_addr.sin_addr);

所以使用 inet_pton 的話,好處就是 linux 跟 windows 都可以通用,所以基本上在 linux 中是建議使用 inet_pton,

inet_pton 使用方法如下,

1
2
3
const char* host = "0.0.0.0";
struct sockaddr_in my_addr;
inet_pton(AF_INET, host, &my_addr.sin_addr); // AF_INET -> ipv4

Linux sokcet 的 sockaddr_in 轉 ip 字串

在 Linux 中 要將 sockaddr_in 結構轉換成 ip 字串的話可用 inet_ntoa (ipv4 only)跟 inet_ntop (ipv4 & ipv6) 兩個 API 達成,

inet_ntoa 用法如下,

1
2
3
struct sockaddr_in client_addr;
char *ip;
ip = inet_ntoa(client_addr.sin_addr)

inet_ntop 用法如下,

1
2
3
struct sockaddr_in client_addr;
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip));

以上就是 C/C++ Linux TCP Socket Server/Client 網路通訊教學,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

Linux hexdiff 比較二進制檔案差異用法與範例

本篇介紹 Linux hexdiff 比較二進制檔案差異用法與範例。

在 Ubuntu 下安裝 hexdiff 的方式為:

1
sudo apt install hexdiff

Linux hexdiff 指令用法範例如下,用 hexdiff 來比較 a.bin 跟 b.bin 兩個二進制檔案內容的差異,

1
hexdiff a.bin b.bin

結果如下圖,

除了 hexdiff 以外,還可以用其他指令來達成同樣的目的,例如:xxd 搭配 diff

1
2
3
$ xxd a.bin > a.hex # 用 xxd 轉為十六進位碼
$ xxd b.bin > b.hex # 用 xxd 轉為十六進位碼
$ diff a.hex b.hex # 用 diff 比較差異

結果如下圖,

以上就是 Linux hexdiff 比較二進制檔案差異用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包

C/C++ fseek 用法與範例

本篇 ShengYu 介紹 C/C++ fseek 的用法與範例,C/C++ 可以使用 fseek 移動檔案指標到某個位置,例如在讀檔想要跳至某個位置讀取時就會用到 fseek,fseek 用法詳見本篇範例。

C/C++ 要使用 fseek 的話需要引入的標頭檔 <stdio.h>,如果要使用 C++ 的標頭檔則是引入 <cstdio>
fseek 函式原型為

1
int fseek(FILE * stream, long offset, int origin);

stream:指向 FILE 物件的指標
offset:從 origin 開始位移,以 byte 為單位
origin:可以是 SEEK_SET, SEEK_CUR, SEEK_END 其中一個

以下 C/C++ fseek 的用法介紹將分為這幾部份,

  • C/C++ fseek 基本用法
  • C/C++ fseek 計算檔案大小
  • C/C++ fseek 計算檔案全部文字再 malloc 配置記憶體

那我們開始吧!

C/C++ fseek 基本用法

這邊介紹 C/C++ fseek 基本用法,以下為 fseek 搭配 ftell 移動檔案指標的各種情況範例,了解這些情況更能幫助了解怎麼使用 fseek,剛開完檔後使用 ftell 會回傳 0,使用 fseek 與 SEEK_SET 參數移動 5 個 bytes 再用 tellg 會回傳 5,再次使用 fseek 與 SEEK_SET 參數移動 5 個 bytes 再用 tellg 還是會回傳 5,說明 SEEK_SET 參數這是移動到一個從檔頭開始的絕對位置而不是前一次的相對位置,移動相對位置的話則要 fseek 搭配 SEEK_CUR 參數就會以當前的位置再開始移動,最後兩個範例分別是移到檔尾跟移到檔頭,

cpp-fseek.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// g++ cpp-fseek.cpp -o a.out
#include <stdio.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

long pos = ftell(fp);
printf("position: %ld\n", pos);

fseek(fp, 5, SEEK_SET);
printf("position: %ld\n", ftell(fp));

fseek(fp, 5, SEEK_SET);
printf("position: %ld\n", ftell(fp));

fseek(fp, 5, SEEK_CUR);
printf("position: %ld\n", ftell(fp));

fseek(fp, 0, SEEK_END); // 移到檔尾
printf("position: %ld\n", ftell(fp));

fseek(fp, 0, SEEK_SET); // 移到檔頭
printf("position: %ld\n", ftell(fp));

fclose(fp);
return 0;
}

假設我的 input.txt 檔案大小是 44 bytes,程式執行結果輸出如下,

1
2
3
4
5
6
position: 0
position: 5
position: 5
position: 10
position: 44
position: 0

C/C++ fseek 計算檔案大小

這邊介紹一下如何利用 fseek 來計算檔案大小,我們可以藉由 fseek 移動檔案指標到檔尾,然後 ftell 取得 size,藉此來知道檔案大小,這種情形通常是在使用 new 或 malloc 動態配置記憶體時需要知道總大小是多少的情況會使用到,

cpp-fseek2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ cpp-fseek2.cpp -o a.out
#include <stdio.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

fseek(fp, 0, SEEK_END); // 移到檔尾
long fsize = ftell(fp);
printf("file size: %ld\n", fsize);
fclose(fp);
return 0;
}

結果輸出如下,

1
file size: 44

C/C++ fseek 計算檔案全部文字再 malloc 配置記憶體

以下示範 C/C++ fseek 移動檔案指標到檔尾,然後計算檔案全部文字後 malloc 配置記憶體,再讀取檔案內容到預先配置好的 buffer 裡,

cpp-fseek3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// g++ cpp-fseek3.cpp -o a.out
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

fseek(fp, 0, SEEK_END); // 移到檔尾
long size = ftell(fp);
printf("size: %ld\n", size);
fseek(fp, 0, SEEK_SET); // 移到檔頭

char *buffer = (char *) malloc(sizeof(char) * size);

fread(buffer, sizeof(char), size, fp);
fclose(fp);

printf("%s", buffer);

free(buffer);

return 0;
}

以上就是 C/C++ fseek 的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ fopen 用法與範例
C/C++ fread 用法與範例
C/C++ fgets 用法與範例
C/C++ fputs 用法與範例
C/C++ fclose 用法與範例

Linux 比較二進制檔案差異的工具

本篇介紹 Linux 比較二進制檔案差異的工具,Linux 比較二進制檔案差異的工具分為這幾部份:

  • xxd 搭配 diff
  • vimdiff
  • hexdump 搭配 diff
  • hexdiff
  • dhex

xxd 搭配 diff

假如我們要以二進制格式比較兩個 a.bin 和 b.bin 檔案的差異,簡單的方法是先使用 xxd 指令將 bin 檔案轉換為文字格式,這裡示範為 .hex 檔案,實際上原理就是用 printf("%02x") 將二進制檔案按 byte 輸出為可讀的資料,接著再使用diff 指令這兩個 hex 檔案的差異,diff 指令只會印出兩個檔案不同之處的數值,xxd 搭配 diff 用法範例如下,

1
2
3
xxd a.bin > a.hex
xxd b.bin > b.hex
diff a.hex b.hex

結果如下圖,

不想另外建立兩個檔的話,可以使用下列一行指令,

1
diff <(xxd a.bin) <(xxd b.bin)

vimdiff

承上例,這邊介紹使用 vimdiff 比對 xxd 的輸出結果,

1
vimdiff <(xxd a.bin) <(xxd b.bin)

結果如下圖,

當然你也可以使用 gvimdiff。

hexdump 搭配 diff

hexdump 也是將檔案 dump 出來的工具,hexdump 搭配 diff 用法如下,

1
2
3
hexdump -C -v a.bin > a.txt
hexdump -C -v b.bin > b.txt
diff a.txt b.txt

不想另外建立兩個檔的話,可以使用下列一行指令,

1
diff <(hexdump -C -v a.bin) <(hexdump -C -v b.bin)

結果如下圖,

hexdiff

在 Ubuntu 下安裝 hexdiff 的方式為:

1
sudo apt install hexdiff

hexdiff 用法範例如下,

1
hexdiff a.bin b.bin

結果如下圖,

dhex

在 Ubuntu 下安裝 dhex 的方式為:

1
sudo apt install dhex

dhex 用法範例如下,

1
dhex a.bin b.bin

結果如下圖,

以上就是 Linux 比較二進制檔案差異的工具介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
Linux 常用指令教學懶人包

GIMP 馬賽克處理

本篇介紹 GIMP 馬賽克處理,GIMP 功能太多,馬賽克處理也是蠻實用的功能,每次都會忘記在選單的哪個位置,這次決定紀錄起來!

開啟要處理的圖片,點選工具箱中的矩形選取工具

選取要馬賽克的範圍,

再點選選單中的濾鏡>模糊化>馬賽克處理

然後會出現馬賽克處理的小視窗,你可以調整賽克格子的像素寬度和像素高度,同時他會出現預覽圖,之後按確定

確定後就可以看到主視窗選取的範圍已經做完了馬賽克處理,

之後如果要再做一樣的馬賽克處理的話,一樣是選取要馬賽克的範圍後,按選單中的濾鏡>重複"馬賽克處理"即可。

以上就是 GIMP 馬賽克處理介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
GIMP 圖片裁切
GIMP 去背/去除背景

webp 轉換 jpg 的 command line 指令

本篇 ShengYu 介紹 webp 轉換 jpg 的 command line 指令用法與範例。

使用 convert 指令將 webp 轉 jpg,因為 convert 依賴 ImageMagick,適用於 ImageMagick v6,Linux 與 macOS 都適用,

1
covert input.webp output.jpg

使用 magick 指令將 webp 轉 jpg,適用於 ImageMagick v7,

1
magick input.webp output.jpg

使用 ffmpeg 指令將 webp 轉 jpg,

1
ffmpeg -i input.webp output.png

使用 dwebp 指令將 webp 轉 jpg,

1
dwebp input.webp -o output.png

使用 cwebp 指令將 webp 轉 jpg,

1
cwebp input.png -o output.webp

以上就是 webp 轉換 jpg 的 command line 指令介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它參考
jpeg - CLI command to convert Webp image(s) to JPG? - Stack Overflow
https://stackoverflow.com/questions/49591274/cli-command-to-convert-webp-images-to-jpg

C/C++ ftell 用法與範例

本篇 ShengYu 介紹 C/C++ ftell 的用法與範例,C/C++ 可以使用 ftell 回傳從檔案頭到當前位置的 byte 數,例如在讀檔時想知道這個檔案裡面有多少個文字時就可以使用 ftell,ftell 用法詳見本篇範例。

C/C++ 要使用 ftell 的話需要引入的標頭檔 <stdio.h>,如果要使用 C++ 的標頭檔則是引入 <cstdio>
ftell 函式原型為

1
long ftell(FILE * stream);

stream:指向 FILE 物件的指標

以下 C/C++ ftell 的用法介紹將分為這幾部份,

  • C/C++ ftell 基本用法
  • C/C++ ftell 計算檔案大小
  • C/C++ ftell 計算檔案全部文字再 malloc 配置記憶體

那我們開始吧!

C/C++ ftell 基本用法

這邊介紹 C/C++ ftell 基本用法,以下為 ftell 搭配 fseek 移動檔案指標的各種情況範例,了解這些情況更能幫助了解怎麼使用 ftell,剛開完檔後使用 ftell 會回傳 0,使用 fseek 與 SEEK_SET 參數移動 5 個 bytes 再用 ftell 會回傳 5,再次使用 fseek 與 SEEK_SET 參數移動 5 個 bytes 再用 ftell 還是會回傳 5,說明 SEEK_SET 參數這是移動到一個從檔頭開始的絕對位置而不是前一次的相對位置,移動相對位置的話則要 fseek 搭配 SEEK_CUR 參數就會以當前的位置再開始移動,最後兩個範例分別是移到檔尾跟移到檔頭,

cpp-ftell.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// g++ cpp-ftell.cpp -o a.out
#include <stdio.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

long pos = ftell(fp);
printf("position: %ld\n", pos);

fseek(fp, 5, SEEK_SET);
printf("position: %ld\n", ftell(fp));

fseek(fp, 5, SEEK_SET);
printf("position: %ld\n", ftell(fp));

fseek(fp, 5, SEEK_CUR);
printf("position: %ld\n", ftell(fp));

fseek(fp, 0, SEEK_END); // 移到檔尾
printf("position: %ld\n", ftell(fp));

fseek(fp, 0, SEEK_SET); // 移到檔頭
printf("position: %ld\n", ftell(fp));

fclose(fp);
return 0;
}

假設我的 input.txt 檔案大小是 44 bytes,程式執行結果輸出如下,

1
2
3
4
5
6
position: 0
position: 5
position: 5
position: 10
position: 44
position: 0

C/C++ ftell 計算檔案大小

這邊介紹一下如何利用 ftell 來計算檔案大小,我們可以藉由 fseek 移動檔案指標到檔尾,然後 ftell 取得 size,藉此來知道檔案大小,這種情形通常是在使用 new 或 malloc 動態配置記憶體時需要知道總大小是多少的情況會使用到,

cpp-ftell2.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// g++ cpp-ftell2.cpp -o a.out
#include <stdio.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

fseek(fp, 0, SEEK_END); // 移到檔尾
long fsize = ftell(fp);
printf("file size: %ld\n", fsize);
fclose(fp);
return 0;
}

結果輸出如下,

1
file size: 44

C/C++ ftell 計算檔案全部文字再 malloc 配置記憶體

以下示範 C/C++ ftell 計算檔案全部文字後再 malloc 配置記憶體,

cpp-ftell3.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// g++ cpp-ftell3.cpp -o a.out
#include <stdio.h>
#include <stdlib.h>

int main() {
FILE *fp = fopen("input.txt", "r");
if (fp == NULL) {
printf("failed to open the file.\n");
return 1; // EXIT_FAILURE
}

fseek(fp, 0, SEEK_END); // 移到檔尾
long size = ftell(fp);
printf("size: %ld\n", size);
fseek(fp, 0, SEEK_SET); // 移到檔頭

char *buffer = (char *) malloc(sizeof(char) * size);

fread(buffer, sizeof(char), size, fp);
fclose(fp);

printf("%s", buffer);

free(buffer);

return 0;
}

以上就是 C/C++ ftell 的用法與範例介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!

其它相關文章推薦
如果你想學習 C++ 相關技術,可以參考看看下面的文章,
C/C++ 新手入門教學懶人包
C/C++ fopen 用法與範例
C/C++ fread 用法與範例
C/C++ fgets 用法與範例
C/C++ fputs 用法與範例
C/C++ fclose 用法與範例

Google Sheet 用替代顏色來幫表格隔行上色/奇偶上色

本篇 ShengYu 介紹 Google Sheet 用替代顏色來幫表格隔行上色/奇偶上色的方法教學。

以下 Google Sheet 用替代顏色來幫表格隔行上色的介紹分為這幾部分,

  • Google Sheet 用替代顏色來幫表格隔行上色
  • Google Sheet 調整上色的顏色樣式
  • Google Sheet 調整上色的頁首或頁尾樣式

那我們開始吧!

Google Sheet 用替代顏色來幫表格隔行上色

首先先選擇好要上色的表格範圍,

接著點選功能選單的格式 > 替代顏色,英文介面的話則是 Format > Alternating colors

這樣就完成表格上色了!是不是很快速!然後畫面右側會出現替代顏色的其他選項,

Google Sheet 調整上色的顏色樣式

我們可以透過右側替代顏色的選項更改表格的顏色樣式,點選下去即可馬上看到效果,

Google Sheet 調整上色的頁首或頁尾樣式

如果要有頁尾的樣式可以勾選右側替代顏色頁尾選項,要取消頁首的樣式也一樣從右側替代顏色頁首選項做取消勾選,

除了預設的顏色樣式以外,你也可以透過自訂樣式來決定每一列的顏色,

以上就是 Google Sheet 用替代顏色來幫表格隔行上色/奇偶上色介紹,
如果你覺得我的文章寫得不錯、對你有幫助的話記得 Facebook 按讚支持一下!