文章目录
一、listen() 函数
1.作用:
listen() 函数把主动连接套接字变为被动连接的套接字,使得这个 socket 可以接受其他 socket 的连接请求,从而成为一个服务端的 socket。
2.函数声明及参数
2.1 函数声明
int listen(int sockfd, int backlog);
2.2 函数参数
(1)参数 sockfd:
参数sockfd是已经被bind过的socket(这个函数是监听,肯定是先绑定了,才监听)。
socket函数返回的socket是一个主动连接的socket,在服务端的编程中,程序员希望这个socket可以接受外来的连接请求,也就是被动等待客户端来连接。由于系统默认时认为一个socket是主动连接的,所以需要通过某种方式来告诉系统,程序员通过调用listen函数来完成这件事。
(2)参数backlog,这个参数涉及到一些网络的细节,比较麻烦,填5、10都行,一般不超过30。
(3) 当调用listen之后,服务端的socket就可以调用accept来接受客户端的连接请求。
返回值:成功则返回0,失败返回-1,错误原因存于errno 中。
listen函数一般不会返回错误。
3.注意事项
当服务端程序还没有开始监听时,客户端请求连接时,会出现无法连接。
测试:
(1)在 listen() 函数前加上 sleep() 让 listen() 函数沉睡一会儿
(2)客户端请求连接
二、accept() 函数
1.作用:
之前说 accept() 函数是接受客户端的连接,准确的说法应该是:从准备好的请求连接队列中取出(获取)一个请求,如果队列为空,accept函数将阻塞等待。
测试:
(1)把 socket 设为监听模式后,让程序沉睡一会儿
(2)如果这个时候客户端请求连接,但是这个连接请求是还没有进到连接队列的,因为我用sleep函数让服务端程序沉睡一会。这个时候队列是空的,要过了sleep规定的时间,连接请求才能进去队列。accept函数才能从中获取连接请求。
客户端请求连接
等待了一段时间,才能从中获取请求连接。
2.函数声明及参数
2.1 函数声明:
根据作用也可以猜测一下这个函数的参数了,客户端请求连接的话,怎么标识客户端?IP地址,所以参数中要有一个参数是用来存放客户端的地址信息的。
socket 通信就是服务端和客户端的两个 socket 进行通信,因为参数中有了一个参数是客户端的地址信息了,那么有一个参数应该是服务端的 socket
accept函数,是从准备好的连接请求队列里面取出一个客户端的请求,并把这个客户端的地址信息放到参数的 sockaddr_in 结构体里面。
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
2.2 函数参数
参数sockfd:
是已经被listen过的socket。
参数addr:
用于存放客户端的地址信息,用sockaddr结构体表达,如果不需要客户端的地址,可以填0。
参数addrlen用于存放addr参数的长度,如果addr为0,addrlen也填0。
accept函数等待客户端的连接,如果没有客户端连上来,它就一直等待,这种方式称之为阻塞。
accept等待到客户端的连接后,创建一个新的socket,函数返回值就是这个新的socket,服务端使用这个新的socket和客户端进行报文的收发。
返回值:
成功则返回0,失败返回-1,错误原因存于errno 中。
accept在等待的过程中,如果被中断或其它的原因,函数返回-1,表示失败,如果失败,可以重新accept。
2.3 注意
int clientfd; // 客户端的socket。
int socklen=sizeof(struct sockaddr_in); // struct sockaddr_in的大小
struct sockaddr_in clientaddr; // 客户端的地址信息。
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
看这段代码,为什么还要定义一个 clientfd socket 呢?直接用 listenfd socket 来通信(发送接收报文)不行吗?
重新定义一个用于通信的 socket 的主要原因是 :有时候连接服务端的客户端不止一个,如果只用 listenfd socket 通信是不行的,多于一个客户端就分不清是谁发过来的数据了。
所以这段代码通常是和循坏一起使用的,创建多个通信的socket
clientfd=accept(listenfd,(struct sockaddr *)&clientaddr,(socklen_t*)&socklen);
三、connect() 函数
1.作用
客户端向服务器发起连接请求。
2.函数声明及参数
根据函数的作用,我们可以猜出,函数的参数有哪些。这个函数是客户端用来连接服务端的,所以要有服务端的地址信息(sockaddr),还有客户端的信息(客户端的信息放在socket里面)
int connect (int sockfd, struct sockaddr * serv_addr, int addrlen);
(1)函数说明:
connect 函数用于将参数 sockfd 的socket 连接至参数 serv_addr 指定的服务器(就是将客户端的socket连接到服务器)。参数 addrlen 为 sockaddr 的结构长度。
(2)返回值:
成功则返回0,失败返回-1,错误原因存于 errno 中。
(3)connect 函数只用于客户端,因为是客户端去连接。如果服务端的地址错了,或端口号错了,或服务端没有启动,connect 一定会失败。
四、总结
1.服务端在调用 listen() 之前,客户端不能向服务端发起连接请求。(这个就像是银行里的前台还没开始上班,就不要去请求别人的帮助)
2.服务端调用 listen() 函数后,服务端的socket开始监听客户端的连接。(到了上班的时间,前台开始工作,等待用户)
3.客户端调用 connect() 函数向服务端发起连接请求。
4.在TCP底层,客户端和服务端握手后建立起通信通道,如果有多个客户端请求,在服务端就会形成一个已准备好的连接的队列。
5.服务端调用 accept() 函数从队列中获取一个已准备好的连接,函数返回一个新的 socket ,新的 socket 用于与客户端通信,listen 的 socket 只负责监听客户端的连接请求。