赞
踩
图解 Socket 函数:
Socket 是应用程序使用网络通信时向系统发送的请求,请求操作系统将网络通信所需的资源。分配给该应用程序返回一个 socket 描述符,随后的连接建立、数据传输等操作都是通过该Socket实现。
函数原型:
int socket(int af, int type, int protocol);
客户端调用 socket() 函数创建套接字后,因为没有建立连接,所以套接字处于CLOSED状态。
socket() 函数用来创建套接字,确定套接字的各种属性,然后服务器端要用 bind() 函数将套接字与特定的IP地址和端口绑定起来,只有这样,流经该IP地址和端口的数据才能交给套接字处理;而客户端要用 connect() 函数建立连接。
函数原型:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
sock 为 socket 文件描述符,addr 为 sockaddr 结构体变量的指针,addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
下面的代码,将创建的套接字与端口 4000 绑定:
int httpd = 0;
struct sockaddr_in name;
// 创建套接字
httpd = socket(PF_INET, SOCK_STREAM, 0);
if (httpd == -1)
error_die("socket");
memset(&name, 0, sizeof(name));
name.sin_family = AF_INET;
name.sin_port = htons(4000);
// INADDR_ANY 0.0.0.0 表示本机的所有 IP
name.sin_addr.s_addr = htonl(INADDR_ANY);
// 将套接字和IP、端口绑定
if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
error_die("bind");
其中 sockaddr_in 结构表示 Socket 套接字地址,包含地址类型、端口号、IP地址、填充字节这4种数据:
connect() 函数用来建立连接,它的原型为:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
对于服务器端程序,使用 bind() 绑定套接字后,还需要使用 listen() 函数让套接字进入被动监听状态,再调用 accept() 函数,就可以随时响应客户端的请求了。通过 listen() 函数可以让套接字进入被动监听状态,它的原型为:
int listen(int sock, int backlog);
sock 为需要进入监听状态的套接字,backlog 为请求队列的最大长度。所谓被动监听,是指当没有客户端请求时,套接字处于“睡眠”状态,只有当接收到客户端请求时,套接字才会被“唤醒”来响应请求。
请求队列:当套接字正在处理客户端请求时,如果有新的请求进来,套接字是没法处理的,只能把它放进缓冲区,待当前请求处理完毕后,再从缓冲区中读取出来处理。如果不断有新的请求进来,它们就按照先后顺序在缓冲区中排队,直到缓冲区满。这个缓冲区,就称为请求队列(Request Queue)。
缓冲区的长度(能存放多少个客户端请求)可以通过 listen() 函数的 backlog 参数指定,但究竟为多少并没有什么标准,可以根据你的需求来定,并发量小的话可以是10或者20。
如果将 backlog 的值设置为 SOMAXCONN,就由系统来决定请求队列长度,这个值一般比较大,可能是几百,或者更多。当请求队列满时,就不再接收新的请求,对于 Linux,客户端会收到 ECONNREFUSED 错误
注意:listen() 只是让套接字处于监听状态,并没有接收请求。接收请求需要使用 accept() 函数。当套接字处于监听状态时,可以通过 accept() 函数来接收客户端请求。它的原型为:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
它的参数与 listen() 和 connect() 是相同的:sock 为服务器端套接字,addr 为 sockaddr_in 结构体变量,addrlen 为参数 addr 的长度,可由 sizeof() 求得。accept() 返回一个新的套接字来和客户端通信,addr 保存了客户端的IP地址和端口号,而 sock 是服务器端的套接字,大家注意区分。后面和客户端通信时,要使用这个新生成的套接字,而不是原来服务器端的套接字。
最后需要说明的是:listen() 只是让套接字进入监听状态,并没有真正接收客户端请求,listen() 后面的代码会继续执行,直到遇到 accept()。accept() 会阻塞程序执行(后面代码不能被执行),直到有新的请求到来。
Linux下数据的接收和发送,Linux 不区分套接字文件和普通文件,使用 write() 可以向套接字中写入数据,使用 read() 可以从套接字中读取数据。
两台计算机之间的通信相当于两个套接字之间的通信,在服务器端用 write() 向套接字写入数据,客户端就能收到,然后再使用 read() 从套接字中读取出来,就完成了一次通信。
write() 的原型为:
ssize_t write(int fd, const void *buf, size_t nbytes);
fd 为要写入的文件的描述符,buf 为要写入的数据的缓冲区地址,nbytes 为要写入的数据的字节数。write() 函数会将缓冲区 buf 中的 nbytes 个字节写入文件 fd,成功则返回写入的字节数,失败则返回 -1。
read() 的原型为:
ssize_t read(int fd, void *buf, size_t nbytes);
fd 为要读取的文件的描述符,buf 为要接收数据的缓冲区地址,nbytes 为要读取的数据的字节数。read() 函数会从 fd 文件中读取 nbytes 个字节并保存到缓冲区 buf,成功则返回读取到的字节数(但遇到文件结尾则返回0),失败则返回 -1。
每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。
write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。
read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
I/O缓冲区特性:
- I/O缓冲区在每个TCP套接字中单独存在;
- I/O缓冲区在创建套接字时自动生成;
- 即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
- 关闭套接字将丢失输入缓冲区中的数据。
输入输出缓冲区的默认大小一般都是 8K,可以通过 getsockopt() 函数获取:
unsigned optVal;
int optLen = sizeof(int);
getsockopt(servSock, SOL_SOCKET, SO_SNDBUF, (char*)&optVal, &optLen);
printf("Buffer length: %d\n", optVal);
对于 TCP 套接字(默认情况下),当使用 write()/send() 发送数据时:
当使用 read()/recv() 读取数据时:
这就是 TCP 套接字的阻塞模式。所谓阻塞,就是上一步动作没有完成,下一步动作将暂停,直到上一步动作完成后才能继续,以保持同步性。TCP 套接字默认情况下是阻塞模式.
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main(int argc, char *argv[]) { int sockfd; int len; struct sockaddr_in address; int result; char ch = 'A'; sockfd = socket(AF_INET, SOCK_STREAM, 0); // 建立 socket 套接字 address.sin_family = AF_INET; address.sin_addr.s_addr = inet_addr("127.0.0.1"); address.sin_port = htons(4000); len = sizeof(address); result = connect(sockfd, (struct sockaddr *)&address, len); // 建立连接 if (result == -1) { perror("oops: client1"); exit(1); } write(sockfd, &ch, 1); // 请求数据 read(sockfd, &ch, 1); // 回应数据 printf("char from server = %c\n", ch); close(sockfd); // 结束连接 exit(0); }
int main(void) { int server_sock = -1; u_short port = 4000; int client_sock = -1; struct sockaddr_in client_name; socklen_t client_name_len = sizeof(client_name); pthread_t newthread; server_sock = startup(&port); // 建立 http 连接 printf("httpd running on port %d\n", port); while (1) { client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len); // 接收客户端请求 if (client_sock == -1) error_die("accept"); // 建立消息请求队列 if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0) perror("pthread_create"); } close(server_sock); // 断开连接 return(0); }
初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。