Head First C 第十一章 网络与套接字 创建knock-knock 服务器 part I
关于套接字
套接字是一种数据流。
用途:不同计算机通过互联网进行通信。
服务器可以与多个客户端进行通信,客户端与服务器将展开一段结构化对话,被称为协议。协议通畅有一套严格的规则,客户端和服务器都严格遵守这套规则就没事,只要其中一方违反了规则,对话就会戛然而止。
BLAB:服务器连接网络四部曲
为了与外界沟通,C程序用数据流读写字节。到目前为止,我们使用过文件,标准输入,标准输出三种数据流。如果想与网络通信,就需要引入一种新的数据流——套接字。
在使用套接字与客户端程序通信前,服务器要经历四个阶段:绑定(Bind)、监听(Listen)、接受(Accept)、开始(Begin),首字母缩写为BLAB。
绑定端口
计算机可能运行多个服务器程序,为了防止不同对话发生混淆,每项服务必须使用不同的端口。服务器在启动时,需要告诉系统要使用哪个端口,这个过程叫做端口绑定,knock-knock服务器将使用30000端口,为了绑定它,我们需要两样东西:套接字描述符和套接字名。套接字名是一个表示“互联网30000端口”的结构。
-
创建套接字描述符
int listener_d = socket(PF_INET, SOCK_STREAM, 0); if (listener_d == -1) { error("Can't open socket"); } int reuse = 1; if (setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(int)) == -1) error("Can't set the reuse option on the socket");
这步操作是要打开套接字,socket函数接收3个参数
int socket(int domain, int type, int protocol);
分别是:域、类型和协议,我们选用的PF_INET表示使用ipv4协议族,SOCKADDR_STREAM表示序列化的、可靠的、双向通信的字节流。0代表的是协议,协议用来制定一个特定的用于当前socket的协议,通常只用已有的协议族里的一个协议。
另外,绑定端口有延时:当你在某个端口绑定了套接字,在接下来的30秒内,操作系统不允许任何程序再绑定它,包括上一次绑定这个端口的程序。所以当你关闭服务器再立即重启,在很多系统下很可能会失败。所以我们要设置reuse:
setsockopt(listener_d, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse,sizeof(int))
该函数接收5个参数,分别是:套接字描述符、级别、设置选项、要设置的值和选项长度。
-
创建套接字名
struct sockaddr_in name; name.sin_family = PF_INET; name.sin_port = (in_addr_t)htons(30000); name.sin_addr.s_addr = htonl(INADDR_ANY);
创建一个sockaddr_in套接字,然后指定协议,端口。
-
绑定
int c = bind(listener_d, (struct sockaddr *)&name, sizeof(name)); if (c == -1) error("Can't bind port");
绑定函数接收套接字描述符、套接字名的地址,以及套接字名的长度。
监听端口
如果有多个客户端来连接你的服务器,那么客户端需要排队,可以用listen()
来告诉系统希望队列有多长:
if (listen(listener_d, 10) == -1)
error("Can't listen port");
puts("waiting for connection...");
接受连接
一旦绑定端口,设置完等待队列,唯一可以做的就是等待。accept()
系统调用会一直等待,直到有客户端连接服务器时,它会返回第二个套接字描述符,然后就可以用它通信了。
struct sockaddr_storage client_addr;
unsigned int address_size = sizeof(client_addr);
int connect_d = accept(listener_d, (struct sockaddr *)&client_addr, &address_size);
if (connect_d == -1)
error("Can't open storage socket");
accept()
系统调用接收三个参数:已创建并绑定端口的socket、连接实体的地址和连接实体的长度。
开始通信
套接字不是传统意义上的数据流
到目前为止,我们见过的数据流都一样,不管是连接文件的数据流,还是标准输入输出数据流,都可以用fprintf()
和fscanf()
和它们通信。但套接字有一点点不同,它既可以作为输入,也可以作为输出,也就是说,要用其它函数与它通信。
如果想向套接字输出数据,就要用send()
函数:
char *msg = "Internet Knock-Knock Protocol Server\r\nVersion 1.0\r\nKnock!Knock!\r\n>";
if(send(connect_d, msg, strlen(msg), 0) == -1)
error("Send");
一定要检查系统调用的返回值,
send()
也不例外。网络错误随处可见,服务器必须处理它们。