目录
一,函数清单
1.socket 方法
2.bind 方法
3.listen 方法
4.accept 方法(阻塞函数)
5.recv 方法(阻塞函数)
6.send 方法
7.close 方法
8.htonl 方法
9.htons 方法
10.fcntl 方法
二,代码实现
1.阻塞型服务端
2.非阻塞型服务端
一,函数清单
1.socket 方法
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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 <sys/types.h> /* See NOTES */ #include <sys/socket.h> 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 <sys/types.h> /* See NOTES */ #include <sys/socket.h> int listen(int sockfd, int backlog);
功能
- 开始监听指定套接字上的连接请求。
参数
- sockfd:socket返回的套接字描述符。
- backlog:等待连接队列的最大长度。如果连接请求到达时如果队列已满,则客户端可能会收到ECONNREFUSED指示的错误,如果底层协议支持重传,则请求可能已满忽略,以便稍后重试连接成功。
返回值
- 如果成功,则返回0。如果出现错误,则返回-1,并设置errno。
4.accept 方法(阻塞函数)
#include <sys/types.h> /* See NOTES */ #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
功能
- 接受一个连接请求,返回一个新的套接字描述符与客户端通信。
参数
- sockfd:socket返回的套接字描述符。
- addr:指向用于存放客户端地址的结构体的指针。通常指定为 struct sockaddr_in 结构体。
- addrlen:用于传递addr结构体的长度。
返回值
- 如果成功,这些系统调用将返回被接受套接字的文件描述符(一个非负整数)。如果出现错误,则返回-1,适设置errno,并且保持addrlen不变。
5.recv 方法(阻塞函数)
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
功能
- 从已连接的套接字接收数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:接收数据的缓冲区。
- len:缓冲区的长度。
- flags:接收操作的标志,一般设置为0。
返回值
- 返回接收到的字节数,如果发生错误则返回-1。如果发生错误,则设置errno来指示错误。当客户端连接关闭时,返回值将为0。
6.send 方法
#include <sys/types.h> #include <sys/socket.h> ssize_t send(int sockfd, const void *buf, size_t len, int flags);
功能
- 向已连接的套接字发送数据。
参数
- sockfd:accept返回的套接字描述符。
- buf:包含要发送数据的缓冲区。
- len:要发送的数据长度。
- flags:发送操作的标志,一般设置为0。
返回值
- 如果成功,这些调用将返回发送的字节数。如果出现错误,则返回-1,并设置errno。
7.close 方法
#include <unistd.h> int close(int fd);
功能
- 关闭文件描述符
参数
- fd:要关闭的文件描述符。
返回值
- 成功返回零。如果出现错误,则返回-1,并设置errno。
8.htonl 方法
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong);
功能
- 将无符号整数hostlong从主机字节顺序转换为网络字节顺序。
9.htons 方法
#include <arpa/inet.h> uint16_t htons(uint16_t hostshort);
功能
- 将无符号短整数hostshort从主机字节顺序转换为网络字节顺序。
10.fcntl 方法
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
功能
- 操作文件描述符的特性,可设置成非阻塞IO。
参数
- fd:要设置的文件描述符。
- cmd:对fd要执行操作的命令,比如F_GETFL,F_SETFL。
返回值
- 对于成功的调用,返回值取决于操作命令,如果出现错误,则返回-1,并适当地设置errno。
二,代码实现
1.阻塞型服务端
代码
- accept和recv都是阻塞型的函数,在accept上是阻塞客户端的连接,在recv上是阻塞读取已连接客户端的数据。
- 为实现连续和客户端进行通信,必须将recv放在一个master循环里,用于一直读取客户端发来的数据。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #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,10)) { 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) {//master循环:因为recv是阻塞函数,为实现连续性收发,必须放在while循环里 char data[BUFFER_LENGTH]={0}; int recvLen = recv(cfd,data,BUFFER_LENGTH,0); //阻塞在cfd里是否有数据可读 if(recvLen < 0){ printf("recv client fd %d errno: %d\n",cfd,errno); }else if(recvLen == 0){ printf("client fd %d close\n",cfd); break; }else{ printf("recv client fd %d data: %s\n",cfd,data); send(cfd,data,recvLen,0); } } return 0; }
运行
- 测试工具:NetAssist 模拟客户端工具,测试服务端代码。
2.非阻塞型服务端
代码
- 在accept的相应代码前添加如下代码,但不要直接在上述代码中添加,不然可能未及时等到客户端连接,使accept返回-1。
int flags = fcntl(sfd,F_GETFL,0); //sfd为服务端文件描述符 flags |= O_NONBLOCK; fcntl(sfd,F_SETFL,flags);
- 以下是用于测试上面这一段代码是否将服务端设置成非阻塞型。
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #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,10)) { 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); return 0; }
运行