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









