- 英文小册原文地址: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网络编程设计师!