ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好。
使用read()/write()和recv()/send()时最好统一。
send:是一个系统调用函数,用来发送消息到一个套接字中,和write,sendto功能相似。
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
说明:
send()函数只能在套接字处于连接状态的时候才能使用。(只有这样才知道接受者是谁)
send和write的唯一区别就是最后一个参数:flags的存在,当我们设置flags为0时,send和wirte是同等的。
// 下面的两个函数等效: write(sockfd,buf,le); send(sockfd,buf,len,0);
ssize_t类型在之前的read/write中描述符哦,相当于long。
sockfd:接收消息的套接字的文件描述符。
buf:要发送的消息。
len:要发送的字节数。
flags:flags参数表示下列标志中的0个或多个
MSG_CONFIRM :用来告诉链路层,
MSG_DONTROUTE:不要使用网关来发送数据,只发送到直接连接的主机上。通常只有诊断或者路由程序会使用,
这只针对路由的协议族定义的,数据包的套接字没有。
MSG_DONTWAIT :启用非阻塞操作,如果操作阻塞,就返回EAGAIN或EWOULDBLOCK //常用
MSG_EOR :当支持SOCK_SEQPACKET时,终止记录。
MSG_MORE :调用方有更多的数据要发送。这个标志与TCP或者udp套接字一起使用
MSG_NOSIGNAL :当另一端中断连接时,请求不向流定向套接字上的错误发送SIGPIPE ,EPIPE 错误仍然返回。
MSG_OOB:在支持此概念的套接字上发送带外数据(例如,SOCK_STREAM类型);底层协议还必须支持带外数据 //常用
拓展:
这里只描述同步socket的send函数的执行流程。
s:套接字 | buf:存储发送数据 | len:发送数据长度
当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完;
如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完.
如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意:并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。
如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;
如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
recv:是一个系统调用函数,用来发送消息到一个套接字中,和write,sendto功能相似。
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
返回值:
成功:实际接收的字节数
失败:-1, 并设置errno
sockfd : 指定接收端套接字描述符
buffer : 接收缓冲区首地址
length : 接收的字节数
flags : 接收方式(通常为0)
拓展
这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
s:套接字 | buf:存储发送数据 | len:发送数据长度
如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,
如果s的发送缓冲中没有数 据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,
如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕。
当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,
所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,
真正的接收数据是协议来完成的recv函数返回其实际copy的字节数。
如果recv在copy时出错,那么它返回SOCKET_ERROR;
如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
这两个函数一般在使用UDP协议时使用
#include <sys/types.h> #include <sys/socket.h> ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
sendto函数如果在连接状态(SOCK_STREAM, SOCK_SEQPACKET)下使用,后面的两个参数dest_addr和addrlen被忽略的,如果不把他们设置成null和0,
会返回错误EISCONN,如果把他们设置成NULL和0,发现实际上不是连接状态,会返回错误EISCONN。正常连接下,它和send同等。
//下面的两个也是等效的: send(sockfd, buf, len, flags); sendto(sockfd, buf, len, flags, NULL, 0);
其他情况下,目标地址是由dest_addr给出,addrlen指定其大小。
#include <sys/types.h> #include <sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
参数描述
sockfd :发送端套接字描述符
buffer : 接收缓冲区首地址
length : 接收的字节数
flags : 接收方式(通常为0)
src_addr :该变量保存源机的IP地址及端口号
addrlen : 常置为sizeof(struct sockaddr)或sizeof(struct sockaddr_in) 与发送方配套即可
返回值:
成功:实际发送的字节数
失败:-1, 并设置errno
addrlen包含实际存入from中的数据字节数
值得注意的是,当你对于数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,
但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
#include <unistd.h> int close(int fd);
用途:关闭读写通信。
#include <sys/socket.h> int shutdown(int sockfd, int how);
参数
howto = 0
关闭读通道,但是可以继续往套接字写数据。
howto = 1
和上面相反,关闭写通道。只能从套接字读取数据。
howto = 2
关闭读写通道,和close()一样
注意
在多进程的情况下,如果有几个子进程共享一个套接字时,如果我们使用shutdown(), 那么所有的子进程都不能够操作了,
这个时候我们只能够使用close()来关闭子进程的套接字描述符。
UDP循环服务器模型: socket(...); bind(...); while(1) { recvfrom(...); process(...); sendto(...); }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int main() { int fd = -1; int result = 0; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ int fd_client; pid_t pid; clientaddrlen = sizeof(clientscokaddr); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } char ipv4_addr[16]; if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); } close(fd); return 0; }
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; /* 在启动程序时 要加上IP 端口,如:test.out 192.168.1.31 5001 从而提高程序通用性*/ /* 下面的程序是用来检测输入的参数是否合法 */ #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(10); } close(fd); return 0; }
上一个server代码通过轮询的方式实现了接收/回复数据,但是有个问题,
当处理过程复杂 需要一段时间才能处理完的的时候,则会造成资源浪费无法快速响应等问题。
为了解决这个问题,可以采用resv-process结构,即:接收采用轮询,处理则创建进程的方式来解决问题。
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <signal.h> #include <sys/wait.h> #define SERV_PORT 5050 #define SERV_IP_ADDR "192.168.0.158" int fun_process(int fd,char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen); void handler(int signo) { //自动回收子进程 防止僵尸进程 if (signo == SIGCHLD) { printf("Recall SIGINT\n"); waitpid(-1, NULL, WNOHANG); } } int main() { int fd = -1; int result = 0; pid_t pid; struct sockaddr_in mysockaddr; struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; char botton[64]; /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*优化4: 允许绑定地址快速重用 */ int b_reuse = 1; setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &b_reuse, sizeof(int)); /*2. 绑定 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT);//网络字节序的端口号 mysockaddr.sin_addr.s_addr = htonl(INADDR_ANY); /*2.2 绑定 */ result = bind(fd, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); if (result != 0) { perror("bind"); return -1; } /*3. 调用recvfrom 接收客户端发来的信息 */ /*优化2:通过程序获取刚建立连接的socket的客户端的IP地址和端口号*/ clientaddrlen = sizeof(clientscokaddr); signal(SIGCHLD, handler); while (1) { bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, 0, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); if (-1 == result) { perror("recvfrom"); break; } pid = fork(); if (pid < 0) { perror("fork"); break; } else if (0 == pid) { result = fun_process(fd, botton,clientscokaddr, clientaddrlen); close(fd); return result; } } close(fd); return 0; } int fun_process(int fd, char* botton,sockaddr_in clientscokaddr, socklen_t clientaddrlen) { char ipv4_addr[16]; int result; char botton_re[64] = "recv success!"; printf("23333\n"); if (!inet_ntop(AF_INET, (void*)&clientscokaddr.sin_addr, ipv4_addr, clientaddrlen)) { perror("inet_ntop"); return -1; } printf("Clinet(%s:%d) is connected!\n", ipv4_addr, ntohs(clientscokaddr.sin_port)); printf("message is:%s\n", botton); do { result = sendto(fd, botton_re, strlen(botton_re), 0, (struct sockaddr*)&clientscokaddr, sizeof(clientscokaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); return -1; } return 0; }
与server改进版 相对应
#include <cstdio> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #define DEBUG 0 void usage(char* s) { printf("\nThis is udp demo!\n"); printf("\nUsage:\n\t %s serv_ip serv_port", s); printf("\n\t serv_ip: udp server ip address"); printf("\n\t serv_port: udp server port(serv_port > 5000)\n\n"); } int main(int argc, char* argv[]) { int fd = -1; int result = 0; int SERV_PORT = -1; char* SERV_IP_ADDR = NULL; struct sockaddr_in mysockaddr; char MESSAGE[] = "hELLo wOLd!!!\n"; char botton[64] = {0}; #if DEBUG SERV_PORT = 5050; SERV_IP_ADDR = "192.168.0.158"; #else if (argc != 3) { usage(argv[0]); return -1; } SERV_PORT = atoi(argv[2]); if (SERV_PORT <= 5000) { usage(argv[0]); return -1; } SERV_IP_ADDR = argv[1]; #endif /* 1. 创建socket fd */ fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket"); return -1; } /*2.连接服务器 写入数据 */ /*2.1 填充struct sockaddr_in结构体变量 */ bzero((void*)&mysockaddr, sizeof(mysockaddr)); mysockaddr.sin_family = AF_INET; mysockaddr.sin_port = htons(SERV_PORT); //网络字节序的端口号 #if 0 mysockaddr.sin_addr.s_addr = inet_addr(SERV_IP_ADDR); #else result = inet_pton(AF_INET, SERV_IP_ADDR, (void*)&mysockaddr.sin_addr); if (result != 1) { perror("inet_pton"); return -1; } #endif /*2.2 读写数据*/ printf("Client staring...OK!\n"); struct sockaddr_in clientscokaddr; socklen_t clientaddrlen; clientaddrlen = sizeof(clientscokaddr); while (1) { do { result = sendto(fd, MESSAGE, strlen(MESSAGE), 0, (struct sockaddr*)&mysockaddr, sizeof(mysockaddr)); } while (result < 0 && EINTR == errno); //判断是否阻塞了 if (result < 0) { perror("connect"); break; } sleep(1); bzero((void*)botton, 64); bzero((void*)&clientscokaddr, clientaddrlen); do { result = recvfrom(fd, botton, 63, MSG_DONTWAIT, (struct sockaddr*)&clientscokaddr, &clientaddrlen); } while (result < 0 && EINTR == errno); printf("message is:%s\n", botton); } close(fd); return 0; }