在TCP/IP協議中,IP地址+TCP/UDP端口號標示網絡通信中唯一的進程,“IP+端口號”稱爲socket。
1.網絡字節序
發送主機通常將發送緩衝區中的數據按內存地址從低到高的順序發出,接收主機把從網絡上接到的字節依次保存在接收緩衝區中,也是按內存地址從低到高的順序保存,因此,絡數據流的地址應這樣規定:先發出的數據是低地址,後發出的數據是地高址。
TCP/IP協議規定,網絡數據流應採用大端字節序,即低地址高字節
爲使網絡程序具有可移植性,使同樣的C代碼在大端和小端計算機上編譯後都能正常運,可以調用以下庫函數做網絡字節序和主機字節序的轉換。
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
2.socket地址的數據類型及相關函數
IPv4地址用sockaddr_in結構體表,包括16位端號口和32位IP地址,IPv6地址用sockaddr_in6結構體表,包括16位端口號、 128位IP地址和一些控制字段。 UNIX Domain Socket的地址格式定義在sys/un.h中,用sockaddr_un結構體表示。各 種socket地址結構體的開頭都是相同的,前16位表示整個結構體的長度(並不是所有UNIX的實現 都有長度字段,如Linux就沒有),後16位表示地址類型。 IPv4、 IPv6和UNIX
Domain Socket的地址類型分別定義爲常數AF_INET、 AF_INET6、 AF_UNIX。
struct sockaddr_in { sa_family_t sin_family; /* address family: AF_INET */ in_port_t sin_port; /* port in network byte order */ struct in_addr sin_addr; /* internet address */};/* Internet address. */ struct in_addr { uint32_t s_addr; /* address in network byte order */};
字符串與in_addr之間的轉換函數:
#include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); const char *inet_ntop(int af, const void *src,char *dst, socklen_t size); #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int inet_aton(const char *cp, struct in_addr *inp); in_addr_t inet_addr(const char *cp); in_addr_t inet_network(const char *cp); char *inet_ntoa(struct in_addr in); struct in_addr inet_makeaddr(int net, int host); in_addr_t inet_lnaof(struct in_addr in);
其中inet_pton和inet_ntop不僅可以轉換IPv4的in_addr,還可以轉換IPv6的in6_addr,因此函數接是void *addrptr。
3.socket中TCP三次握手建立連接與四次握手釋放連接
三次握手:當客戶端調用connect時,觸發了連接請求,向服務器發送了SYN J包,這時connect進入阻塞狀態;服務器監聽到連接請求,即收到SYN J包,調用accept函數接收請求向客戶端發送SYN K ,ACK J+1,這時accept進入阻塞狀態;客戶端收到服務器的SYN K ,ACK J+1之後,這時connect返回,並對SYN K進行確認;服務器收到ACK K+1時,accept返回,至此三次握手完畢,連接建立。
四次握手:
1.應用進程首先調用close主動關閉連接,這時TCP發送一個FIN M。
2.另一端接收到FIN M之後,執行被動關閉,對這個FIN進行確認。它的接收也作爲文件結束符傳遞給應用進程,因爲FIN的接收意味着應用進程在相應的連接上再也接收不到額外數據;
3.一段時間之後,接收到文件結束符的應用進程調用close關閉它的socket。這導致它的TCP也發送一個FIN N;
4.接收到這個FIN的源發送端TCP對它進行確認。
這樣每個方向上都有一個FIN和ACK。
最簡單的TCP網絡程序
下面通過最簡單的客戶端/服務器程序的實例來學習socket API。
server.c 的作用是接受client的請求,並與client進程簡單的數據通信,整體爲一個阻塞式的網絡聊天工具。
server.c
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #define _PORT_ 9999 #define _BACKLOG_ 10 int main() { //建立鏈接 int sock = socket(AF_INET, SOCK_STREAM, 0); if(sock < 0) { perror("socket"); return 1; } //監聽 struct sockaddr_in sock_server; bzero(&sock_server, sizeof(sock_server)); sock_server.sin_family = AF_INET; sock_server.sin_port = htons(_PORT_); sock_server.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(sock, (struct sockaddr*)&sock_server, sizeof(sock_server)) < 0) { perror("bind"); close(sock); return 2; } if(listen(sock, _BACKLOG_) < 0) { perror("listen"); close(sock); return 3; } printf("bind and listen success,wait acceot...\n"); struct sockaddr_in sock_client; socklen_t len = 0; while(1) { int sock_cl = accept(sock, (struct sockaddr*)&sock_client, &len); if(sock_cl < 0) { perror("accept"); close(sock); return 4; } char buf_id[INET_ADDRSTRLEN]; memset(buf_id, '\0', sizeof(buf_id)); inet_ntop(AF_INET, &sock_client.sin_addr, buf_id, sizeof(buf_id)); printf("get connect,id is :%s port is :%d\n",buf_id, ntohs(sock_client.sin_port)); while(1) { char buf[1024]; memset(buf, '\0', sizeof(buf)); read(sock_cl, buf, sizeof(buf)); printf("client:#%s\n", buf); printf("server:$"); memset(buf, '\0', sizeof(buf)); fgets(buf, sizeof(buf), stdin); buf[strlen(buf) - 1] = '\0'; write(sock_cl, buf, strlen(buf) + 1); printf("please wait..\n"); } } close(sock); return 0; }
client.c
#include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <strings.h> #include <arpa/inet.h> #define _PORT_ 9999 #define SERVER_IP "192.168.52.128 " int main(int argc, char* argv[]) { if(argc != 2) { printf("Usage:client IP\n"); return 1; } char* str = argv[1]; char buf[1024]; memset(buf, '\0', sizeof(buf)); struct sockaddr_in server_sock; int sock = socket(AF_INET, SOCK_STREAM, 0); bzero(&server_sock, sizeof(server_sock)); server_sock.sin_family = AF_INET; inet_pton(AF_INET, SERVER_IP, &server_sock.sin_addr); server_sock.sin_port = htons(_PORT_); //鏈接 int ret = connect(sock ,(struct sockaddr*)&server_sock, sizeof(server_sock)); if(ret < 0) { perror("connect"); return 1; } printf("connect success...\n"); while(1) { printf("client:#"); fgets(buf, sizeof(buf), stdin); buf[strlen(buf) - 1] = '\0'; write(sock, buf, sizeof(buf)); if(strncasecmp(buf, "quit", 4) == 0) { printf("quit\n"); break; } printf("please wait...\n"); read(sock, buf, sizeof(buf)); printf("server:$%s\n", buf); } close(sock); return 0; }