目录
一,函数清单
1.socket 方法
2.bind 方法
3.listen 方法
4.accept 方法(阻塞函数)
5.recv 方法(阻塞函数)
6.send 方法
7.close 方法
8.htonl 方法
9.htons 方法
10.fcntl 方法
二,代码实现
1.阻塞型服务端
TCP服务端程序的一般流程
TCP客户端程序的一般流程
完整代码
2.非阻塞型服务端
非阻塞型TCP服务端的一般流程
完整代码
一,函数清单
1.socket 方法
#include /* See NOTES */#include int socket(int domain, int type, int protocol);
功能
- 创建用于通信的套接字,并返回一个指向该套接字的文件描述符。
参数
- domain:指定套接字的协议族。常见的值有AF_INET(IPv4)和AF_INET6(IPv6)。
- type:指定套接字的类型。常见的值有SOCK_STREAM(面向连接的可靠字节流)和SOCK_DGRAM(无连接的数据报文)。
- protocol:指定协议。通常使用0,表示默认选择。
返回值
- 如果成功,则返回新套接字的文件描述符。如果出现错误,则返回-1,并设置errno。
2.bind 方法
#include /* See NOTES */#include int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能
- 将套接字与特定的IP地址和端口绑定。
参数
- sockfd:socket返回的套接字描述符。
- addr:指向要绑定的本地地址的结构体(通常是一个sockaddr_in或sockaddr_in6结构体)。
- addrlen:本地地址的长度(通常是sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6))。
返回值
- 如果成功,则返回0。如果出现错误,则返回-1,并设置errno。
3.listen 方法
#include /* See NOTES */#include int listen(int sockfd, int backlog);
功能
- 开始监听指定套接字上的连接请求。
参数
- sockfd:socket返回的套接字描述符。
- backlog:等待连接队列的最大长度。如果连接请求到达时如果队列已满,则客户端可能会收到ECONNREFUSED指示的错误,如果底层协议支持重传,则请求可能已满忽略,以便稍后重试连接成功。
返回值
- 如果成功,则返回0。如果出现错误,则返回-1,并设置errno。
4.accept 方法(阻塞函数)
#include /* See NOTES */#include int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能
- 接受一个连接请求,返回一个新的套接字描述符与客户端通信。
参数
- sockfd:socket返回的套接字描述符。
- addr:指向用于存放客户端地址的结构体的指针。通常指定为 struct sockaddr_in 结构体。
- addrlen:用于传递addr结构体的长度。
返回值
- 如果成功,这些系统调用将返回被接受套接字的文件描述符(一个非负整数)。如果出现错误,则返回-1,适设置errno,并且保持addrlen不变。
5.recv 方法(阻塞函数)
#include #include ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能
- 从已连接的套接字接收数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:接收数据的缓冲区。
- len:缓冲区的长度。
- flags:接收操作的标志,一般设置为0。
返回值
- 返回接收到的字节数,如果发生错误则返回-1。如果发生错误,则设置errno来指示错误。当客户端连接关闭时,返回值将为0。
6.send 方法
#include #include ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能
- 向已连接的套接字发送数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:包含要发送数据的缓冲区。
- len:要发送的数据长度。
- flags:发送操作的标志,一般设置为0。
返回值
- 如果成功,这些调用将返回发送的字节数。如果出现错误,则返回-1,并设置errno。
7.close 方法
#include int close(int fd);
功能
- 关闭文件描述符,释放相关资源
参数
- fd:要关闭的文件描述符。
返回值
- 成功返回零。如果出现错误,则返回-1,并设置errno。
8.htonl 方法
#include uint32_t htonl(uint32_t hostlong);
功能
- 将一个32位(4字节)的主机字节序的无符号整数转换为网络字节序的整数。
9.htons 方法
#include uint16_t htons(uint16_t hostshort);
功能
- 将一个16位(2字节)的主机字节序的符号短整数转换为网络字节序的整数。
10.fcntl 方法
#include #include int fcntl(int fd, int cmd, ... /* arg */ );
功能
- 操作文件描述符的行为和属性,可设置成非阻塞IO。
参数
- fd:要设置的文件描述符。
- cmd:对fd要执行操作的命令,常见命令如下,通常使用F_GETFL,F_SETFL。
F_DUPFD
:复制文件描述符。F_GETFD
:获取文件描述符标志。F_SETFD
:设置文件描述符标志。F_GETFL
:获取文件状态标志。F_SETFL
:设置文件状态标志。F_GETLK
:获取文件锁。F_SETLK
:设置文件锁。F_SETLKW
:设置文件锁,如果锁不可用则等待。返回值
- 对于成功的调用,返回值取决于操作命令,如果出现错误,则返回-1,并适当地设置errno。
二,代码实现
1.阻塞型服务端
TCP服务端程序的一般流程
创建套接字(Socket):使用
socket
系统调用创建一个TCP套接字。套接字是网络通信的端点。绑定地址和端口(Bind):将服务器的IP地址和端口号与套接字绑定,使用
bind
系统调用来完成绑定操作。监听连接请求(Listen):将套接字置于监听状态,等待客户端的连接请求。使用
listen
系统调用设置套接字的监听队列长度。接受连接请求(Accept):当有客户端请求连接时,使用
accept
系统调用接受连接请求。这将创建一个新的套接字,用于和客户端进行通信,而原始的监听套接字继续监听新的连接请求。进行通信(Communicate):使用接受到的套接字进行数据通信。可以使用
read/recv
和write/send
系统调用或其它高级的I/O函数来收发数据。关闭套接字(Close):当通信结束后,使用
close
系统调用关闭套接字,释放相关资源。示例代码
#include #include #include #include int main() {// 创建套接字int serverSocket = socket(AF_INET, SOCK_STREAM, 0);// 绑定地址和端口struct sockaddr_in serverAddress;serverAddress.sin_family = AF_INET;//ipv4serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);serverAddress.sin_port = htons(8888);bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));// 监听连接请求listen(serverSocket, 5);// 接受连接请求int clientSocket = accept(serverSocket, NULL, NULL);// 进行通信char buffer[1024];read(clientSocket, buffer, sizeof(buffer));printf("Received message: %s\n", buffer);// 关闭套接字close(clientSocket);close(serverSocket);return 0;}
TCP客户端程序的一般流程
创建套接字(Socket):使用
socket
系统调用创建一个TCP套接字。设置服务器地址和端口号:使用
struct sockaddr_in
结构体来表示服务器的地址和端口号。根据服务器的IP地址和端口号来填充该结构体。连接服务器(Connect):使用
connect
系统调用将套接字连接到服务器。将服务器的地址和端口号作为参数传递给connect
函数。进行数据通信(Communicate):使用已连接的套接字进行数据的读取和写入。可以使用
read
和write
系统调用读取和发送数据。关闭套接字(Close):当通信完成后,使用
close
系统调用关闭套接字,释放相关资源。示例代码
#include #include #include #include int main() {// 创建套接字int clientSocket = socket(AF_INET, SOCK_STREAM, 0);// 设置服务器地址和端口号struct sockaddr_in serverAddress;serverAddress.sin_family = AF_INET;serverAddress.sin_port = htons(8888);serverAddress.sin_addr.s_addr = inet_addr("服务器IP地址");// 连接服务器connect(clientSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));// 进行数据通信char *message = "Hello, server!";send(clientSocket, message, strlen(message),0);// 关闭套接字close(clientSocket);return 0;}
完整代码
- accept和recv都是阻塞型的函数,在accept上是阻塞客户端的连接,在recv上是阻塞读取已连接客户端的数据。
- 为实现连续和客户端进行通信,必须将recv放在一个master循环里,用于一直读取客户端发来的数据。
#include #include #include #include #include #include #include #include #define BUFFER_LENGTH 1024//初始化服务端,返回其文件描述符int init_server(int port){//返回服务端fd,通常为3,前面0,1,2用于表示标准输入,输出,错误值int sfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == sfd){printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}struct sockaddr_in servAddr;memset(&servAddr,0,sizeof(struct sockaddr_in));servAddr.sin_family = AF_INET;//ipv4servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0servAddr.sin_port = htons(port);//端口号//绑定IP和端口号if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))){printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}//监听该套接字上的连接if(-1 == listen(sfd,SOMAXCONN)){printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}return sfd;}int main(int argc,char *argv[]){if(argc < 2)return -1;int port = atoi(argv[1]); //atoi:将字符串转换为整数int sfd = init_server(port);printf("server fd: %d\n",sfd);struct sockaddr_in clientAddr;socklen_t len = sizeof(struct sockaddr_in);int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len); //阻塞函数printf("client fd: %d\n",cfd);while (1){char data[BUFFER_LENGTH]={0};int recvLen = recv(cfd,data,BUFFER_LENGTH,0);//阻塞函数if(recvLen < 0){printf("recv client fd %d errno: %d\n",cfd,errno);}else if(recvLen == 0){printf("client fd %d close\n",cfd);close(cfd); //关闭客户端文件描述符,释放资源break;}else{printf("recv client fd %d data: %s\n",cfd,data);send(cfd,data,recvLen,0);}}close(sfd); //关闭服务端文件描述符,释放资源printf("server fd %d close\n",sfd);return 0;}
运行效果
- 测试工具:NetAssist模拟客户端工具,测试服务端代码。
2.非阻塞型服务端
非阻塞型TCP服务端的一般流程
创建套接字(Socket):使用
socket
系统调用创建一个TCP套接字。设置套接字为非阻塞模式:使用
fcntl
函数,通过F_SETFL
命令将套接字的文件状态标志设置为非阻塞模式,即使用O_NONBLOCK
标志。绑定地址和端口(Bind):将服务器的IP地址和端口号与套接字绑定,使用
bind
系统调用来完成绑定操作。监听连接请求(Listen):将套接字置于监听状态,等待客户端的连接请求,使用
listen
系统调用设置套接字的监听队列长度。接受连接请求(Accept):使用
accept
系统调用接受连接请求。这将创建一个新的套接字,用于和客户端进行通信,而原始的监听套接字继续监听新的连接请求。设置新的套接字为非阻塞模式:同样,使用
fcntl
函数设置新的套接字为非阻塞模式。进行数据通信(Communicate):使用非阻塞的套接字进行数据的读取和写入。可以使用
read/recv
和write/send
系统调用或其他非阻塞的I/O函数进行数据交换。关闭套接字(Close):当通信结束后,使用
close
系统调用关闭套接字,释放相关资源。示例代码
#include #include #include #include #include int main() {// 创建套接字int serverSocket = socket(AF_INET, SOCK_STREAM, 0);// 设置套接字为非阻塞模式int flags = fcntl(serverSocket, F_GETFL, 0);fcntl(serverSocket, F_SETFL, flags | O_NONBLOCK);// 绑定地址和端口struct sockaddr_in serverAddress;serverAddress.sin_family = AF_INET;serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);serverAddress.sin_port = htons(8888);bind(serverSocket, (struct sockaddr *)&serverAddress, sizeof(serverAddress));// 监听连接请求listen(serverSocket, 5);while (1) {// 接受连接请求struct sockaddr_in clientAddress;socklen_t clientAddressLength = sizeof(clientAddress);int clientSocket = accept(serverSocket, (struct sockaddr *)&clientAddress, &clientAddressLength);if (clientSocket > 0) {// 设置新的套接字为非阻塞模式flags = fcntl(clientSocket, F_GETFL, 0);fcntl(clientSocket, F_SETFL, flags | O_NONBLOCK);// 进行数据通信char buffer[1024];ssize_t bytesRead = read(clientSocket, buffer, sizeof(buffer));if (bytesRead > 0) {// 读取到数据printf("Received message from client: %s\n", buffer);}// 关闭客户端套接字close(clientSocket);}}// 关闭服务端套接字close(serverSocket);return 0;}
完整代码
- 设置套接字为非阻塞模式。
- 以下是用于测试上面这一段代码是否将服务端设置成非阻塞型。
#include #include #include #include #include #include #include #include #include #define BUFFER_LENGTH 1024int init_server(int port){//获取服务端fd,通常为3,前面0,1,2用于指定输入,输出,错误值int sfd = socket(AF_INET,SOCK_STREAM,0);if(-1 == sfd){printf("socket error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}//设置服务端套接字为非阻塞模式int flags = fcntl(sfd,F_GETFL,0);fcntl(sfd,F_SETFL,flags | O_NONBLOCK);struct sockaddr_in servAddr;memset(&servAddr,0,sizeof(struct sockaddr_in));servAddr.sin_family = AF_INET;//ipv4servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0servAddr.sin_port = htons(port);//服务端绑定ip地址和端口号if(-1 == bind(sfd,(struct sockaddr*)&servAddr,sizeof(struct sockaddr_in))){printf("bind error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}//监听该套接字上的连接请求if(-1 == listen(sfd,SOMAXCONN)){printf("listen error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}return sfd;}int main(int argc,char *argv[]){if(argc < 2)return -1;int port = atoi(argv[1]);int sfd = init_server(port);printf("server fd: %d\n",sfd);//接受连接请求struct sockaddr_in clientAddr;socklen_t len = sizeof(struct sockaddr_in);int cfd = accept(sfd,(struct sockaddr*)&clientAddr,&len);if(cfd == -1){printf("accept error code: %d codeInfo: %s\n",errno,strerror(errno));return -1;}printf("client fd: %d\n",cfd);//设置新的套接字为非阻塞模式int flags = fcntl(cfd,F_GETFL,0);fcntl(cfd,F_SETFL,flags | O_NONBLOCK);while (1){char data[BUFFER_LENGTH]={0};int recvLen = recv(cfd,data,BUFFER_LENGTH,0);if(recvLen < 0){printf("recv client fd %d errno: %d\n",cfd,errno);}else if(recvLen == 0){//客户端断开连接printf("client fd %d close\n",cfd);close(cfd); //关闭客户端文件描述符,释放资源break;}else{printf("recv client fd %d data: %s\n",cfd,data);send(cfd,data,recvLen,0);}}close(sfd); //关闭服务端文件描述符,释放资源printf("server fd %d close\n",sfd);return 0;}
运行效果
- 编译成功并运行后,报了如下错误,导致服务端不能正常运行。
问题分析
- 错误码”Resource temporarily unavailable”(资源暂时不可用)在Linux中对应的错误码为EAGAIN(错误值11)或EWOULDBLOCK。这个错误码通常在非阻塞I/O操作中出现,表示当前没有可用的资源或操作正在进行中。
- 在网络编程中,当使用非阻塞模式的套接字进行读取或写入操作时,如果没有可用的数据或无法立即完成写入操作,就可能会返回这个错误码。这是因为非阻塞模式下的I/O操作是非阻塞的,即它们要么立即完成,要么返回错误码而不等待。
解决方法
异步I/O(Asynchronous I/O):通过使用异步I/O操作,可以在所有I/O操作之后返回,而不会阻塞当前线程。Linux提供了aio_read和aio_write等异步I/O函数。使用异步I/O操作,你可以注册回调函数,在操作完成时接收通知。
后续学习再来处理。。。