目录
一,函数清单
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服务端的一般流程
完整代码
#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。
#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。
#include
/* See NOTES */ #include int listen(int sockfd, int backlog); 功能
- 开始监听指定套接字上的连接请求。
参数
- sockfd:socket返回的套接字描述符。
- backlog:等待连接队列的最大长度。如果连接请求到达时如果队列已满,则客户端可能会收到ECONNREFUSED指示的错误,如果底层协议支持重传,则请求可能已满忽略,以便稍后重试连接成功。
返回值
- 如果成功,则返回0。如果出现错误,则返回-1,并设置errno。
#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不变。
#include
#include ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能
- 从已连接的套接字接收数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:接收数据的缓冲区。
- len:缓冲区的长度。
- flags:接收操作的标志,一般设置为0。
返回值
- 返回接收到的字节数,如果发生错误则返回-1。如果发生错误,则设置errno来指示错误。当客户端连接关闭时,返回值将为0。
#include
#include ssize_t send(int sockfd, const void *buf, size_t len, int flags); 功能
- 向已连接的套接字发送数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:包含要发送数据的缓冲区。
- len:要发送的数据长度。
- flags:发送操作的标志,一般设置为0。
返回值
- 如果成功,这些调用将返回发送的字节数。如果出现错误,则返回-1,并设置errno。
#include
int close(int fd); 功能
- 关闭文件描述符,释放相关资源
参数
- fd:要关闭的文件描述符。
返回值
- 成功返回零。如果出现错误,则返回-1,并设置errno。
#include
uint32_t htonl(uint32_t hostlong); 功能
- 将一个32位(4字节)的主机字节序的无符号整数转换为网络字节序的整数。
#include
uint16_t htons(uint16_t hostshort); 功能
- 将一个16位(2字节)的主机字节序的符号短整数转换为网络字节序的整数。
#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。
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; //ipv4 serverAddress.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; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.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; }
运行效果
非阻塞型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 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; } //设置服务端套接字为非阻塞模式 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; //ipv4 servAddr.sin_addr.s_addr = htonl(INADDR_ANY); //0.0.0.0 servAddr.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操作,你可以注册回调函数,在操作完成时接收通知。
后续学习再来处理。。。