在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; }