0
点赞
收藏
分享

微信扫一扫

从accept()到收发消息


  • 英文小册原文地址:​​beej.us/guide/bgnet…​​
  • 作者:Beej
  • 中文翻译地址:​​www.chanmufeng.com/posts/netwo…​​

accept()

系好安全带,​​accept()​​之旅开始了。

一个远程用户试图调用​​connect()​​​来连接到你使用​​listen()​​​进行监听的端口上,这个连接会被放到队列中等着被你​​accept()​​。这句话要是你看不懂你需要回去看看前文哦。

然后你调用​​accept()​​​,从队列中取出一个等候已久的连接,​​accept()​​会返回给你一个专属于这个连接的一个全新的​socket file descriptor​​!

没错,你现在有2个​socket file descriptor​​​了!原来的​​socket file descriptor​​​仍在处于被​​listen()​​​的状态等待客户端的连接,而你刚刚得到的​​socket file descriptor​​​则是准备给​​send()​​​和​​recv()​​使用的,通过这俩函数,就可以实现和客户端之间的通信了。

​accept()​​使用如下:

#include <sys/types.h>
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t;

​sockfd​​​就是正在被​​listen()​​​的那个​​socket descriptor​​,这个没啥难度。

​addr​​​就是一个指向 ​​struct sockaddr_storage​​的指针,这里边会自动保存客户端的一些信息,你能从中得知客户端是从哪个IP、哪个端口对你发起的连接。

​addrlen​​​是一个整数变量,你应该在将它的地址传给​​accept()​​​之前,把它设置为 ​​sizeof(struct sockaddr_storage)​​​ 。​​accept()​​​保存在​​addr​​​指向的对象中的数据大小只会小于等于​​addrlen​​​,如果小于的话,​​accept()​​​会通过改变​​addrlen​​的值来告诉你,所以你应该知道为什么这个字段是个指针变量了吧。

猜猜我要说啥,一句我快说吐了的话。​​accept() ​​​在错误的时候会返回​​ -1​​​,并设置 ​​errno​​。

和之前一样,show you the code可能会更好吸收,看一段代码:

#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

#define MYPORT "3490" // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold

int main(void)
{
struct sockaddr_storage their_addr;
socklen_t addr_size;
struct addrinfo hints, *res;
int sockfd, new_fd;

// !! don't forget your error checking for these calls !!

// first, load up address structs with getaddrinfo():

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // use IPv4 or IPv6, whichever
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // fill in my IP for me

getaddrinfo(NULL, MYPORT, &hints, &res);

// make a socket, bind it, and listen on it:

sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
bind(sockfd, res->ai_addr, res->ai_addrlen);
listen(sockfd, BACKLOG);

// now accept an incoming connection:

addr_size = sizeof their_addr;
new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &addr_size);

// ready to communicate on socket descriptor new_fd!

接下来我们就会用得到的​​new_fd​​​这个​​socket descriptor​​​进行​​send()​​​和​​recv()​​。

如果你只是想获取这一个连接,你可以使用​​close()​​​来关闭处于​​LISTEN​​​状态的​​sockfd​​​,这样就可以避免有更多的客户端连接到​​3490​​这个端口上了。

send()与recv()

​send()​​​和​​recv()​​​两个函数是服务端和客户端通过​​stream socket​​​或者是​​connected datagram socket​​​用来通信的。如果你想使用常规的​​unconnected datagram socket​​​,你需要参考下文的​​sendto()​​​和​​recvfrom()​​一节了。

​send()​​的声明长这样儿:

int send(int sockfd, const void *msg, int len, int;

​sockfd​​​是一个你想发送数据过去的​​socket descriptor​​​,这个​​socket descriptor​​​可能是​​socket()​​​返回的,也可能是通过​​accept()​​得到的。

​msg​​​是指向发想发送的数据的指针,​​len​​​是数据的字节长度。至于​​flags​​​,你就直接设置成​​0​​​就完了(更多的细节参见后文的​​send()​​手册,PS:还没翻译到那里)。

上例子:

char *msg = "Hello World!";
int len, bytes_sent;
.
.
.
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
.
.
.

​send()​​​的返回值表示实际发送数据的字节数,这个数可能比你设置的想发送的数据长度​​len​​要小。事情就是这么奇怪,明明你想让函数发送所有的数据,它却做不到,它只能尽力把能发送的数据都发送出去,但是它不会自动发送剩下的数据。

因此,如果​​send()​​​返回的值小于​​len​​​的话,就需要由你来决定是不是要发送剩下的数据了。也并非每次都这样,如果数据包很小(小于​​1K​​​或更小),​​send()​可能会一下子把所有数据都发出去。

再强调一遍,​​send()​​​异常的话会返回​​-1​​​,并且修改​​errno​​这个全局变量。

​recv()​​​这个函数在很多方面和​​send()​​类似:

int recv(int sockfd, void *buf, int len, int;

​sockfd​​​是你想从中读取数据的​​socket descriptor​​​,​​buf​​​是存储你读取的数据的缓冲区,​​len​​​是缓冲区的最大长度,​​flags​​​还是直接设置成​​0​​​就行(更多的细节参见之后的​​recv()​​手册,PS:还没翻译到那里)。

​recv()​​​的返回值表示实际读到缓冲区的字节数,异常的话会返回​​-1​​​,并且修改​​errno​​这个全局变量。

注意!​​recv()​​​的返回值可以是​​0​​​,是​​0​​​意味着对面关闭了与你的连接,返回​​0​​是为了让你知道这回事儿。

到这就结束了,很简单对吧。你现在可以通过​​stream socket​​进行往返数据处理了。

恭喜你,正式成为一名UNIX网络编程设计师!

举报

相关推荐

0 条评论