【Linux编程】socket编程
套接字是通信端点的抽象。文件描写叙述符用open函数创建,而套接字描写叙述符用socket函数创建。socket函数原型例如以下:
int socket(int domain, int type, int protocol); // 返回值:成功返回套接字描写叙述符,失败返回-1
domain域确定通信特性。不同的域表示地址的格式不同,表示域的常数以AF开头。表示地址族(address family):
type确定套接字类型。进一步确定通信特征:
protocol通常为0,表示按上述两个參数确定默认的协议。比如:
- 通信域为AF_INET。套接字类型为SOCK_STREAM。则默认协议为TCP。
- 通信域为AF_INET。套接字类型为SOCK_DGRAM。则默认协议为UDP。
关闭套接字的一端可用shutdown函数:
int shutdown(int sockfd, int how); // 返回值:成功返回0,失败返回-1
how为SHUT_RD,关闭读端,无法从套接字读取数据;how为SHUT_WR。关闭写端,无法通过套接字发送数据。
关闭套接字描写叙述符的方法和关闭文件描写叙述符的方法同样,都是使用close函数。
不同的CPU。使用不同的字节序列来表示数据,分大端序和小端序。比如大端序的机器发送到小端序的机器上,则接收到的数据顺序会颠倒。为了避免这样的情况,使网络传输与详细的机器类型无关,採用网络字节序。发送方把本机序转换为网络字节序后发送,接收方把接收到的网络字节序转换为本机序。
TCP/IP採用大端字节序,即网络字节序,而我们经常使用的X86结构是小端模式。下图来自维基百科,描写叙述得非常形象:
TCP/IP应用程序提供了四个通用函数用于本地字节序和网络字节序之间的转换:
- htonl:本地32位整型转网络字节序
- htons:本地16位整型转网络字节序
- ntohl:网络字节序转32位整型本地字节序
- ntohs:网络字节序转16位整型本地字节序
下面是TCP和UDP程序的相关函数接口的调用过程。
1、TCP服务端:
- socket:建立一个传输端点。返回一个套接字描写叙述符。
- bind:将套接字绑定到某个IP和port。此IP和port就作为监听对象。注意,因为系统会分配给client默认地址。所以client使用该函数意义不大。
- listen:将套接字转换成监听套接字,使得client的连接请求可被内核接受,上述三步为设置监听描写叙述符的正常步骤。
- accept:休眠。等待客户连接被内核接受。三次握手完成后返回一个新的套接字描写叙述符。该描写叙述符连接到调用connect的client。被称为已连接描写叙述符。
- send:发数据。
- recv:收数据。
- close:关闭连接,引发四次握手。
2、TCPclient:
- socket:创建套接字。
- connect:建立连接。指明目的,和服务端的accept函数相应。
- send:发数据。
- recv:收数据。
3、UDP服务端:
- socket:建立套接字。
- bind:绑定监听的IP和port。
- recvfrom:堵塞等待数据的到来。
4、UDPclient:
和TCPclient基本一致。假设在发送之前使用了connect函数。那么发送报文的目的地址都是connect调用中所指定的地址。所以直接send函数进行发送。
没用之前没有调用connect函数,发送用sendto函数。这个函数须要在參数中指定目的地址。
下面是一些例程。
TCPclient:
/* 基于TCP协议的socket编程client代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* client: * socket-->connect-->send/recv */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iRet; int iSocketClient; struct sockaddr_in tSocketServerAddr; int iSendLen; unsigned char ucSendBuf[1000]; if (argc != 2) { printf("Usage:\n"); printf("%s <server_ip>\n", argv[0]); return -1; } /* 1. 建立socket */ iSocketClient = socket(AF_INET, SOCK_STREAM, 0); /* 2. 与服务器建立TCP连接 */ tSocketServerAddr.sin_family = AF_INET; /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */ { printf("Invalid server IP\n"); return -1; } bzero(tSocketServerAddr.sin_zero, 8); /* 假设iSocketClient没有绑定地址。则connect会给调用者绑定一个默认地址 */ iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iRet == -1) { /* 连接失败 */ printf("connect error\n"); return -1; } while (1) { if (fgets(ucSendBuf, 999, stdin)) { /* send函数发送数据 */ iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); if (iSendLen == -1) { /* 函数出错 */ close(iSocketClient); return -1; } } } return 0; }
TCP服务端:
/* 基于TCP协议的socket编程server端代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> /* server: * socket-->bind-->listen-->accept-->send/recv */ #define SERVER_PORT 8888 #define BACKLOG 10 int main(int argc, char **argv) { int iRet; int iAddrLen; int iSocketSever; int iNewSocketSever; struct sockaddr_in tSocketServerAddr; struct sockaddr_in tSocketClientAddr; int iRecvLen; unsigned char ucRecvBuf[1000]; /* 子进程死后,会发送SIGCHLD信号给父进程, * 这里设置为忽略SIGCHLD信号 */ signal(SIGCHLD,SIG_IGN); /* 1. 建立socket * AF_INET: IPv4 Internet protocols * SOCK_STREAM: TCP协议 * 0: 通常赋值 */ iSocketSever = socket(AF_INET, SOCK_STREAM, 0); if (iSocketSever == -1) { printf("socket error\n"); return -1; } /* 2. 配置socket * 设置监听本机的哪个IP,端口 */ tSocketServerAddr.sin_family = AF_INET; /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 绑定到本机全部网络接口 */ bzero(tSocketServerAddr.sin_zero, 8); iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iRet == -1) { printf("bind error\n"); return -1; } /* 3. 开启socket监听模式 * 建立服务请求队列,BACKLOG为最大连接数 */ iRet = listen(iSocketSever, BACKLOG); if (iRet == -1) { printf("listen error\n"); return -1; } while (1) { /* 4. 休眠,等待建立连接 * client地址存放在第二个參数中 * 以后用新的socket描写叙述符进行读写操作 */ iAddrLen = sizeof(struct sockaddr); iNewSocketSever = accept(iSocketSever, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); if (iNewSocketSever != -1) { /* 成功建立连接 */ printf("Get connect from IP:%s, port:%d\n", inet_ntoa(tSocketClientAddr.sin_addr), ntohs(tSocketClientAddr.sin_port)); /* net to ascII */ /* fork系统调用创建子进程 */ if (fork() == 0) { /* 子进程源代码 */ while (1) { /* recv函数接收client数据并显示,假设flags为0,则和read、write一样的操作 */ iRecvLen = recv(iNewSocketSever, ucRecvBuf, 999, 0); /* flags普通情况下置为0 */ if (iRecvLen <= 0) { /* recv出错(-1)或连接关闭(0) */ close(iNewSocketSever); printf("Disconnected!\n"); return -1; } else { ucRecvBuf[iRecvLen] = '\0'; printf("The recv msg is : %s\n", ucRecvBuf); } } } else { /* 父进程代码 */ close(iNewSocketSever); /* 父进程关闭已连接描写叙述符尤为重要 */ } } } close(iSocketSever); return 0; }
注意,假设使用了SOCK_STREAM套接字。就不能保证一次可以接受到整个数据。
普通情况是要在循环中重复读。直到返回0.
UDPclient:
/* 基于UDP协议的socket编程client代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* client: * socket-->connect-->send/recv */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iRet; int iSocketClient; struct sockaddr_in tSocketServerAddr; int iSendLen; unsigned char ucSendBuf[1000]; if (argc != 2) { printf("Usage:\n"); printf("%s <server_ip>\n", argv[0]); return -1; } /* 1. 建立socket */ iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); /* 2. 不会建立真正的连接,仅仅是让socket描写叙述符中带有服务器地址, * 假设不使用connect函数,则应该使用sento函数发送数据 */ tSocketServerAddr.sin_family = AF_INET; /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */ { printf("Invalid server IP\n"); return -1; } bzero(tSocketServerAddr.sin_zero, 8); /* UDP中不须要建立连接,但这里使用connect是能够将地址绑定到iSocketClient上,send函数中就不须要地址信息了 */ iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iRet == -1) { printf("connect error\n"); return -1; } while (1) { if (fgets(ucSendBuf, 999, stdin)) { /* send函数发送数据 */ iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0); if (iSendLen == -1) { /* 函数出错 */ close(iSocketClient); return -1; } } } return 0; }
UDP服务端:
/* 基于UDP协议的socket编程server端代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> /* server: * socket-->bind-->sendto/recvfrom */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iRet; int iAddrLen; int iSocketSever; struct sockaddr_in tSocketServerAddr; struct sockaddr_in tSocketClientAddr; int iRecvLen; unsigned char ucRecvBuf[1000]; /* 1. 建立socket * AF_INET: IPv4 Internet protocols * SOCK_DGRAM: UDP协议 * 0: 通常赋值 */ iSocketSever = socket(AF_INET, SOCK_DGRAM, 0); if (iSocketSever == -1) { printf("socket error\n"); return -1; } /* 2. 配置socket * 设置监听本机的哪个IP,端口 */ tSocketServerAddr.sin_family = AF_INET; /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ tSocketServerAddr.sin_addr.s_addr = htonl(INADDR_ANY); /* 填入本机全部IP */ bzero(tSocketServerAddr.sin_zero, 8); iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iRet == -1) { printf("bind error\n"); return -1; } while (1) { /* 接收client数据,地址信息放在tSocketClientAddr结构体中 */ iAddrLen = sizeof(struct sockaddr); iRecvLen = recvfrom(iSocketSever, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); if (iRecvLen > 0) { ucRecvBuf[iRecvLen] = '\0'; printf("Get msg from %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf); } } close(iSocketSever); return 0; }
UDPclient(无connect函数):
/* 基于UDP协议的socket编程client代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* client: * socket-->sendto/recvfrom */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iRet; int iSocketClient; struct sockaddr_in tSocketServerAddr; int iSendLen; unsigned char ucSendBuf[1000]; if (argc != 2) { printf("Usage:\n"); printf("%s <server_ip>\n", argv[0]); return -1; } /* 1. 建立socket */ iSocketClient = socket(AF_INET, SOCK_DGRAM, 0); /* 2. 设置服务器地址 */ tSocketServerAddr.sin_family = htonl(AF_INET); /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ if (inet_aton(argv[1], &tSocketServerAddr.sin_addr) == 0) /* 字符串转服务器IP */ { printf("Invalid server IP\n"); return -1; } bzero(tSocketServerAddr.sin_zero, 8); while (1) { if (fgets(ucSendBuf, 999, stdin)) { /* sendto函数发送数据 */ iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iSendLen == -1) { /* 函数出错 */ close(iSocketClient); return -1; } } } return 0; }
UDP服务端(无connect函数):
/* 基于UDP协议的socket编程server端代码 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h> /* server: * socket-->bind-->sendto/recvfrom */ #define SERVER_PORT 8888 int main(int argc, char **argv) { int iRet; int iAddrLen; int iSocketSever; struct sockaddr_in tSocketServerAddr; struct sockaddr_in tSocketClientAddr; int iRecvLen; unsigned char ucRecvBuf[1000]; /* 1. 建立socket * AF_INET: IPv4 Internet protocols * SOCK_DGRAM: UDP协议 * 0: 通常赋值 */ iSocketSever = socket(AF_INET, SOCK_DGRAM, 0); if (iSocketSever == -1) { printf("socket error\n"); return -1; } /* 2. 配置socket * 设置监听本机的哪个IP,端口 */ tSocketServerAddr.sin_family = AF_INET; /* recommend */ tSocketServerAddr.sin_port = htons(SERVER_PORT); /* host to net short */ tSocketServerAddr.sin_addr.s_addr = INADDR_ANY; /* 填入本机全部IP */ bzero(tSocketServerAddr.sin_zero, 8); iRet = bind(iSocketSever, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr)); if (iRet == -1) { printf("bind error\n"); return -1; } while (1) { /* 接收client数据。地址信息放在tSocketClientAddr结构体中 */ iAddrLen = sizeof(struct sockaddr); iRecvLen = recvfrom(iSocketSever, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen); if (iRecvLen > 0) { ucRecvBuf[iRecvLen] = '\0'; printf("Get msg from %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf); } } close(iSocketSever); return 0; }
參考:
《unix环境高级编程》第16章。