Linux 系統程式設計 學習:8-基於socket的網路程式設計3:基於 TCP 的通訊
知識
TCP(Transmission Control Protoco 傳輸控制協議)。
TCP是一種面向廣域網的通訊協議,目的是在跨越多個網路通訊時,為兩個通訊端點之間提供一條具有下列特點的通訊方式:
-
基於流的方式;
-
面向連線;
-
可靠通訊方式;
-
在網路狀況不佳的時候儘量降低系統由於重傳帶來的頻寬開銷;
-
通訊連線維護是面向通訊的兩個端點的,而不考慮中間網段和節點。
為滿足TCP協議的這些特點,TCP協議做了如下的規定:
- 資料分片:在傳送端對使用者資料進行分片,在接收端進行重組,由TCP確定分片的大小並控制分片和重組;
- 到達確認:接收端接收到分片資料時,根據分片資料序號向傳送端傳送一個確認;
- 超時重發:傳送方在傳送分片時啟動超時定時器,如果在定時器超時之後沒有收到相應的確認,重發分片;
- 滑動視窗:TCP連線每一方的接收緩衝空間大小都固定,接收端只允許另一端傳送接收端緩衝區所能接納的資料,TCP在滑動視窗的基礎上提供流量控制,防止較快主機致使較慢主機的緩衝區溢位;
- 失序處理:作為IP資料報來傳輸的TCP分片到達時可能會失序,TCP將對收到的資料進行重新排序,將收到的資料以正確的順序交給應用層;
- 重複處理:作為IP資料報來傳輸的TCP分片會發生重複,TCP的接收端必須丟棄重複的資料;
- 資料校驗:TCP將保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,並不確認收到此報文段導致對端超時並重發。
有關函式介紹
根據流程圖,我們知道,在UDP通訊中,使用到了這些函式:socket()
、bind()
、sendto()
、recvfrom()
。
上面的函式我們在《基於UDP 的通訊》中已經講過,這裡不再重複了。
在TCP中,多了這幾個函式:listen()
、connect()
、accept()
。
伺服器呼叫listen
監聽 客戶端的connect
listen
成功時,伺服器使用由accept
獲取到的新的套接字進行通訊。
當客戶端呼叫connect函式時,將引發三次握手過程:客戶端首先發送SYN請求分組,此時服務端會將請求放入SYN佇列,同時向客戶端傳送ACK確認報文,然後客戶端向服務端再次傳送ACK報文。服務端收到ACK確認報文後,將SYN裡的連線請求移入ACCEPT佇列。此時三次握手結束,即TCP連線成功建立。然後核心通知使用者空間的阻塞的服務程序,服務程序呼叫accept僅僅是從ACCEPT佇列裡取出一個連線而已。也就是說客戶端呼叫connect連線伺服器,與伺服器呼叫accept“接受”連線是兩個獨立的過程。
listen
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intlisten(int sockfd, int backlog);
描述:將尚未建立連線的socket轉換為被動socket,並監聽發給這個被動socket的connect請求。
引數解析:
sockfd:由socket
函式成功返回的值
backlog :核心應該為相應套介面排隊的最大連線個數(不是用來限制socket的最大連線數),一般為以下兩個佇列的大小之和,即未完成三次握手佇列 + 已經完成三次握手佇列。即:TCP模組允許的已完成三次握手過程(TCP模組完成)但還沒來得及被應用程式accept的最大連結數。
核心為任何一個給定的監聽套介面維護兩個佇列:
1、未完成連線佇列(incomplete connection queue),每個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三次握手過程。這些套介面處於SYN_RCVD狀態。
2、已完成連線佇列(completed connection queue),每個已完成TCP三次握手過程的客戶對應其中一項。這些套介面處於ESTABLISHED狀態。
當來自客戶的SYN到達時,TCP在 未完成連線佇列 中建立一個新項,然後響應以三次握手的第二個分節:伺服器的SYN響應,其中稍帶對客戶SYN的ACK(即SYN+ACK)。這一項一直保留在未完成連線佇列中,直到三路握手的第三個分節(客戶對伺服器SYN的ACK)到達或者該項超時為止(曾經源自Berkeley的實現為這些未完成連線的項設定的超時值為75秒)。如果三路握手正常完成,該項就從未完成連線佇列移到已完成連線佇列的隊尾。當程序呼叫accept時,已完成連線佇列中的隊頭項將返回給程序,或者如果該佇列為空,那麼程序將被投入睡眠,直到TCP在該佇列中放入一項才喚醒它。
返回值:成功返回0,失敗返回-1,置errno:
- EADDRINUSE:另一個套接字已在同一埠上偵聽。
- EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前沒有繫結到地址,在嘗試將其繫結到臨時埠時,確定臨時埠範圍中的所有埠號當前都在使用中。
- EBADF:引數sockfd不是有效的描述符。
- ENOTSOCK:檔案描述符sockfd沒有引用套接字。
- EOPNOTSUPP:套接字的型別不支援listen()操作。
主動socket和被動socket
一般來說,使用socket函式建立的socket預設是主動socket,這意味著一個主動的socket可以呼叫connect
跟一個被動socket建立一個連線,對主動socket來說,這叫主動開啟。
被動socket是一個通過呼叫listen
函式監聽要發起連線的socket,當被動socket接受一個連線通常稱為被動開啟。
在大多數網路程式中,服務端會作為被動socket被動接受連線,而客戶端會作為主動socket主動發起連線。
服務端通過socket函式建立的socket是主動socket,而listen函式就是把這個還未接受連線的主動socket轉換為被動socket,因為服務端只需要被動接受客戶端的連線請求。
Linux系統設定未連線佇列最大數限制
linux系統tcp/ip協議棧有個選項可以設定未連線佇列大小限制tcp_max_syn_backlog
可以通過命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog
檢視
Linux 系統中提供somaxconn
這個引數,它定義了系統中每一個埠最大的監聽佇列的長度,這是個全域性的引數,預設值為128
可以通過命令:cat /proc/sys/net/core/somaxconn
檢視
connect
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intconnect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
描述:連線一個被動socket
引數解析:
sockfd:主動socket
addr:目的地址
addrlen:地址屬性的長度(addr的大小)
返回值:成功返回0,失敗返回-1,置errno:
- EAFNOSUPPORT:傳遞的地址在其sau family欄位中沒有正確的地址系列。
- EAGAIN :路由快取中的條目不足。
- EALREADY:套接字未阻塞,上一次連線嘗試尚未完成。
EBADF:檔案描述符不是描述符表中的有效索引。 - ECONNREFUSED:沒有人監聽遠端地址。
- EFAULT :套接字結構地址在使用者的地址空間之外。
- EINPROGRESS:套接字未阻塞,無法立即完成連線。可以通過選擇要寫入的套接字來選擇(2)或輪詢(2)以完成。
- EINTR :系統呼叫被捕獲的訊號中斷。
- EISCONN:套接字已連線。
- ENETUNREACH:無法訪問網路。
- ENOTSOCK:sockfd不是套接字。
- EPROTOTYPE:套接字型別不支援請求的通訊協議。例如,在嘗試將UNIX域資料報套接字連線到流套接字時,可能會發生此錯誤。
- ETIMEDOUT:嘗試連線時超時。伺服器可能太忙,無法接受新連線。請注意,對於IP套接字,當伺服器上啟用Syncookie時,超時可能非常長。
accept
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include<sys/socket.h>
intaccept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
描述:從核心的ACCEPT佇列中取出對應被動socket的連線,關於連線的有關屬性填入addr中。
引數解析:
sockfd:對應的被動socket
addr:儲存連線方的addr屬性的容器
len:addr屬性的長度
返回值:成功返回可用於連線的新socket,失敗返回-1,置errno:
此外,可能會返回新套接字的網路錯誤以及為協議定義的網路錯誤。各種Linux核心可以返回其他錯誤,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟蹤期間可以看到值ERESTARTSYS。
-
EMFILE :已達到開啟的檔案描述符數的每個程序限制
-
ENFILE :已達到系統範圍內開啟檔案總數的限制
-
ENOBUFS, ENOMEM:沒有足夠的可用記憶體。這通常意味著記憶體分配受到套接字緩衝區限制,而不是系統記憶體的限制
-
ENOTSOCK sockfd不是套接字
-
EOPNOTSUPP 引用的套接字不是SOCK_STREAM型別
-
EPROTO :協議錯誤
-
EPERM (Linux) :防火牆規則禁止連線
例程
我們簡單地進行一次TCP對答通訊的實現
server.c
c/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: server.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
intmain(int argc, char *argv[]){
int my_socket;
unsigned int len;
int ret;
// 建立套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用於接收訊息
info buf ={0};
// 指定地址
structsockaddr_inaddr = {0};
addr.sin_family = AF_INET; // 地址協議族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定埠號
// 伺服器 繫結
bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));
// my_socket 只用於監聽
ret = listen(my_socket, 10);
if(-1 == ret) { perror("listen"); }
printf("Listening\n");
int new_socket;
structsockaddr_innew = {0};
int new_addr_size;
// accept以後會返回一個新的套接字,用於與客戶端通訊
new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
printf("New socket is %d\n", new_socket);
perror("accept");
// 接收並列印訊息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(new_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 回覆訊息
sprintf(buf.name, "Server");
sprintf(buf.text, "Had recvied your message");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(new_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 關閉連線
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
close(new_socket); perror("close");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}
client.c
c/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: client.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
intmain(int argc, char *argv[]){
int my_socket;
unsigned int len;
int ret;
// 建立套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用於接收訊息
info buf ={0};
// 指定地址
structsockaddr_inaddr = {0};
addr.sin_family = AF_INET; // 地址協議族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定埠號
// 用於連線伺服器
connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
if(-1 == ret) { perror("connect"); }
printf("connected\n");
// 回覆訊息
sprintf(buf.name, "Client");
sprintf(buf.text, "Hello tcp text.");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(my_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 接收並列印訊息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(my_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 關閉連線
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}
知識
TCP(Transmission Control Protoco 傳輸控制協議)。
TCP是一種面向廣域網的通訊協議,目的是在跨越多個網路通訊時,為兩個通訊端點之間提供一條具有下列特點的通訊方式:
-
基於流的方式;
-
面向連線;
-
可靠通訊方式;
-
在網路狀況不佳的時候儘量降低系統由於重傳帶來的頻寬開銷;
-
通訊連線維護是面向通訊的兩個端點的,而不考慮中間網段和節點。
為滿足TCP協議的這些特點,TCP協議做了如下的規定:
- 資料分片:在傳送端對使用者資料進行分片,在接收端進行重組,由TCP確定分片的大小並控制分片和重組;
- 到達確認:接收端接收到分片資料時,根據分片資料序號向傳送端傳送一個確認;
- 超時重發:傳送方在傳送分片時啟動超時定時器,如果在定時器超時之後沒有收到相應的確認,重發分片;
- 滑動視窗:TCP連線每一方的接收緩衝空間大小都固定,接收端只允許另一端傳送接收端緩衝區所能接納的資料,TCP在滑動視窗的基礎上提供流量控制,防止較快主機致使較慢主機的緩衝區溢位;
- 失序處理:作為IP資料報來傳輸的TCP分片到達時可能會失序,TCP將對收到的資料進行重新排序,將收到的資料以正確的順序交給應用層;
- 重複處理:作為IP資料報來傳輸的TCP分片會發生重複,TCP的接收端必須丟棄重複的資料;
- 資料校驗:TCP將保持它首部和資料的檢驗和,這是一個端到端的檢驗和,目的是檢測資料在傳輸過程中的任何變化。如果收到分片的檢驗和有差錯,TCP將丟棄這個分片,並不確認收到此報文段導致對端超時並重發。
有關函式介紹
根據流程圖,我們知道,在UDP通訊中,使用到了這些函式:socket()
、bind()
、sendto()
、recvfrom()
。
上面的函式我們在《基於UDP 的通訊》中已經講過,這裡不再重複了。
在TCP中,多了這幾個函式:listen()
、connect()
、accept()
。
伺服器呼叫listen
監聽 客戶端的connect
;listen
成功時,伺服器使用由accept
獲取到的新的套接字進行通訊。
當客戶端呼叫connect函式時,將引發三次握手過程:客戶端首先發送SYN請求分組,此時服務端會將請求放入SYN佇列,同時向客戶端傳送ACK確認報文,然後客戶端向服務端再次傳送ACK報文。服務端收到ACK確認報文後,將SYN裡的連線請求移入ACCEPT佇列。此時三次握手結束,即TCP連線成功建立。然後核心通知使用者空間的阻塞的服務程序,服務程序呼叫accept僅僅是從ACCEPT佇列裡取出一個連線而已。也就是說客戶端呼叫connect連線伺服器,與伺服器呼叫accept“接受”連線是兩個獨立的過程。
listen
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intlisten(int sockfd, int backlog);
描述:將尚未建立連線的socket轉換為被動socket,並監聽發給這個被動socket的connect請求。
引數解析:
sockfd:由socket
函式成功返回的值
backlog :核心應該為相應套介面排隊的最大連線個數(不是用來限制socket的最大連線數),一般為以下兩個佇列的大小之和,即未完成三次握手佇列 + 已經完成三次握手佇列。即:TCP模組允許的已完成三次握手過程(TCP模組完成)但還沒來得及被應用程式accept的最大連結數。
核心為任何一個給定的監聽套介面維護兩個佇列:
1、未完成連線佇列(incomplete connection queue),每個這樣的SYN分節對應其中一項:已由某個客戶發出併到達伺服器,而伺服器正在等待完成相應的TCP三次握手過程。這些套介面處於SYN_RCVD狀態。
2、已完成連線佇列(completed connection queue),每個已完成TCP三次握手過程的客戶對應其中一項。這些套介面處於ESTABLISHED狀態。
當來自客戶的SYN到達時,TCP在 未完成連線佇列 中建立一個新項,然後響應以三次握手的第二個分節:伺服器的SYN響應,其中稍帶對客戶SYN的ACK(即SYN+ACK)。這一項一直保留在未完成連線佇列中,直到三路握手的第三個分節(客戶對伺服器SYN的ACK)到達或者該項超時為止(曾經源自Berkeley的實現為這些未完成連線的項設定的超時值為75秒)。如果三路握手正常完成,該項就從未完成連線佇列移到已完成連線佇列的隊尾。當程序呼叫accept時,已完成連線佇列中的隊頭項將返回給程序,或者如果該佇列為空,那麼程序將被投入睡眠,直到TCP在該佇列中放入一項才喚醒它。
返回值:成功返回0,失敗返回-1,置errno:
- EADDRINUSE:另一個套接字已在同一埠上偵聽。
- EADDRINUSE:(Internet域套接字)sockfd引用的套接字以前沒有繫結到地址,在嘗試將其繫結到臨時埠時,確定臨時埠範圍中的所有埠號當前都在使用中。
- EBADF:引數sockfd不是有效的描述符。
- ENOTSOCK:檔案描述符sockfd沒有引用套接字。
- EOPNOTSUPP:套接字的型別不支援listen()操作。
主動socket和被動socket
一般來說,使用socket函式建立的socket預設是主動socket,這意味著一個主動的socket可以呼叫connect
跟一個被動socket建立一個連線,對主動socket來說,這叫主動開啟。
被動socket是一個通過呼叫listen
函式監聽要發起連線的socket,當被動socket接受一個連線通常稱為被動開啟。
在大多數網路程式中,服務端會作為被動socket被動接受連線,而客戶端會作為主動socket主動發起連線。
服務端通過socket函式建立的socket是主動socket,而listen函式就是把這個還未接受連線的主動socket轉換為被動socket,因為服務端只需要被動接受客戶端的連線請求。
Linux系統設定未連線佇列最大數限制
linux系統tcp/ip協議棧有個選項可以設定未連線佇列大小限制tcp_max_syn_backlog
可以通過命令:cat /proc/sys/net/ipv4/tcp_max_syn_backlog
檢視
Linux 系統中提供somaxconn
這個引數,它定義了系統中每一個埠最大的監聽佇列的長度,這是個全域性的引數,預設值為128
可以通過命令:cat /proc/sys/net/core/somaxconn
檢視
connect
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intconnect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
描述:連線一個被動socket
引數解析:
sockfd:主動socket
addr:目的地址
addrlen:地址屬性的長度(addr的大小)
返回值:成功返回0,失敗返回-1,置errno:
- EAFNOSUPPORT:傳遞的地址在其sau family欄位中沒有正確的地址系列。
- EAGAIN :路由快取中的條目不足。
- EALREADY:套接字未阻塞,上一次連線嘗試尚未完成。
EBADF:檔案描述符不是描述符表中的有效索引。 - ECONNREFUSED:沒有人監聽遠端地址。
- EFAULT :套接字結構地址在使用者的地址空間之外。
- EINPROGRESS:套接字未阻塞,無法立即完成連線。可以通過選擇要寫入的套接字來選擇(2)或輪詢(2)以完成。
- EINTR :系統呼叫被捕獲的訊號中斷。
- EISCONN:套接字已連線。
- ENETUNREACH:無法訪問網路。
- ENOTSOCK:sockfd不是套接字。
- EPROTOTYPE:套接字型別不支援請求的通訊協議。例如,在嘗試將UNIX域資料報套接字連線到流套接字時,可能會發生此錯誤。
- ETIMEDOUT:嘗試連線時超時。伺服器可能太忙,無法接受新連線。請注意,對於IP套接字,當伺服器上啟用Syncookie時,超時可能非常長。
accept
c#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include<sys/socket.h>
intaccept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
描述:從核心的ACCEPT佇列中取出對應被動socket的連線,關於連線的有關屬性填入addr中。
引數解析:
sockfd:對應的被動socket
addr:儲存連線方的addr屬性的容器
len:addr屬性的長度
返回值:成功返回可用於連線的新socket,失敗返回-1,置errno:
此外,可能會返回新套接字的網路錯誤以及為協議定義的網路錯誤。各種Linux核心可以返回其他錯誤,例如ENOSR、ESOCKTNOSUPPORT、EPROTONOSUPPORT、ETIMEDOUT。在跟蹤期間可以看到值ERESTARTSYS。
-
EMFILE :已達到開啟的檔案描述符數的每個程序限制
-
ENFILE :已達到系統範圍內開啟檔案總數的限制
-
ENOBUFS, ENOMEM:沒有足夠的可用記憶體。這通常意味著記憶體分配受到套接字緩衝區限制,而不是系統記憶體的限制
-
ENOTSOCK sockfd不是套接字
-
EOPNOTSUPP 引用的套接字不是SOCK_STREAM型別
-
EPROTO :協議錯誤
-
EPERM (Linux) :防火牆規則禁止連線
例程
我們簡單地進行一次TCP對答通訊的實現
server.c
c/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: server.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
intmain(int argc, char *argv[]){
int my_socket;
unsigned int len;
int ret;
// 建立套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用於接收訊息
info buf ={0};
// 指定地址
structsockaddr_inaddr = {0};
addr.sin_family = AF_INET; // 地址協議族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定埠號
// 伺服器 繫結
bind(my_socket, (struct sockaddr *)&addr, sizeof(addr));
// my_socket 只用於監聽
ret = listen(my_socket, 10);
if(-1 == ret) { perror("listen"); }
printf("Listening\n");
int new_socket;
structsockaddr_innew = {0};
int new_addr_size;
// accept以後會返回一個新的套接字,用於與客戶端通訊
new_socket = accept(my_socket, (struct sockaddr*)&new, &new_addr_size);
printf("New socket is %d\n", new_socket);
perror("accept");
// 接收並列印訊息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(new_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 回覆訊息
sprintf(buf.name, "Server");
sprintf(buf.text, "Had recvied your message");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(new_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 關閉連線
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
close(new_socket); perror("close");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}
client.c
c/*
# Copyright By Schips, All Rights Reserved
# https://gitee.com/schips/
#
# File Name: client.c
# Created : Sat 21 Mar 2020 04:43:39 PM CST
*/
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h> /* See NOTES */
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct _info {
char name[10];
char text[54];
}info;
intmain(int argc, char *argv[]){
int my_socket;
unsigned int len;
int ret;
// 建立套接字
my_socket = socket(AF_INET, SOCK_STREAM, 0); // IPV4, TCP socket
if(my_socket == -1) { perror("Socket"); }
printf("Creat a socket :[%d]\n", my_socket);
// 用於接收訊息
info buf ={0};
// 指定地址
structsockaddr_inaddr = {0};
addr.sin_family = AF_INET; // 地址協議族
addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //指定 IP地址
addr.sin_port = htons(12345); //指定埠號
// 用於連線伺服器
connect(my_socket, (struct sockaddr *)(&addr), sizeof(struct sockaddr_in));
if(-1 == ret) { perror("connect"); }
printf("connected\n");
// 回覆訊息
sprintf(buf.name, "Client");
sprintf(buf.text, "Hello tcp text.");
//sendto(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
send(my_socket, &buf, sizeof(buf), 0);
perror("sendto");
// 接收並列印訊息
//recvfrom(my_socket, &buf, sizeof(buf), 0, NULL, NULL);
recv(my_socket, &buf, sizeof(buf), 0);
perror("recvfrom");
printf("%s: %s\n", buf.name, buf.text);
// 關閉連線
//shutdown(my_socket, SHUT_RDWR); perror("shutdown");
return close(my_socket); perror("close");
printf("%d\n", errno);
return errno;
}