http://www.ibm.com/developerworks/cn/linux/l-sockpit/。
bind 普遍遭遇的问题是试图绑定一个已经在使用的端口。该陷阱是也许没有活动的套接字存在,但仍然禁止绑定端口(bind 返回 EADDRINUSE),它由 TCP 套接字状态 TIME_WAIT 引起。该状态在套接字关闭后约保留 2 到 4 分钟。在 TIME_WAIT 状态退出之后,套接字被删除,该地址才能被重新绑定而不出问题。
等待 TIME_WAIT 结束可能是令人恼火的一件事,特别是如果您正在开发一个套接字服务器,就需要停止服务器来做一些改动,然后重启。幸运的是,有方法可以避开 TIME_WAIT 状态。可以给套接字应用 SO_REUSEADDR 套接字选项,以便端口可以马上重用
然后 有下面代码产生避免上面的问题:
- #include “sys/types.h”
- #include "sys/socket.h"
- #include " stdio.h“
- #include "netinet/in.h"
- #include "arpa/inet.h"
- #include "unistd.h"
- #include "stdlib.h"
- #define BUFFER_SIZE 40
- int main()
- {
- char buf[BUFFER_SIZE];
- int server_sockfd, client_sockfd;
- int sin_size=sizeof(struct sockaddr_in);
- struct sockaddr_in server_address;
- struct sockaddr_in client_address;
- memset(&server_address,0,sizeof(server_address));
- server_address.sin_family = AF_INET;
- server_address.sin_addr.s_addr = INADDR_ANY;
- server_address.sin_port = htons(12000);
- // 建立服务器端socket
- if((server_sockfd = socket(AF_INET, SOCK_STREAM, 0))<0)
- {
- perror("server_sockfd creation failed");
- exit(EXIT_FAILURE);
- }
- // 设置套接字选项避免地址使用错误
- int on=1;
- if((setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)
- {
- perror("setsockopt failed");
- exit(EXIT_FAILURE);
- }
- // 将套接字绑定到服务器的网络地址上
- if((bind(server_sockfd,(struct sockaddr *)&server_address,sizeof(struct sockaddr)))<0)
- {
- perror("server socket bind failed");
- exit(EXIT_FAILURE);
- }
- // 建立监听队列
- listen(server_sockfd,5);
- // 等待客户端连接请求到达
- client_sockfd=accept(server_sockfd,(struct sockaddr *)&client_address,(socklen_t*)&sin_size);
- if(client_sockfd<0)
- {
- perror("accept client socket failed");
- exit(EXIT_FAILURE);
- }
- // 接收客户端数据
- if(recv(client_sockfd,buf,BUFFER_SIZE,0)<0)
- {
- perror("recv client data failed");
- exit(EXIT_FAILURE);
- }
- printf("receive from client:%s/n",buf);
- // 发送数据到客户端
- if(send(client_sockfd,"I have received your message.",BUFFER_SIZE,0)<0)
- {
- perror("send failed");
- exit(EXIT_FAILURE);
- }
- close(client_sockfd);
- close(server_sockfd);
- exit(EXIT_SUCCESS);
- }
常用socket函数详解
关于socket函数,每个的意义和基本功能都知道,但每次使用都会去百度,参数到底是什么,返回值代表什么意义,就是说用的少,也记得不够精确。每次都查半天,经常烦恼于此。索性都弄得清楚、通透,并记录下来,一来便于自己记忆,再者以防日后查阅、回顾。
主要介绍:socket、bind、listen、connect、accept、send、sendto、recv、recvfrom、close、shutdown
网络中的进程是通过socket来通信的,那什么是socket呢?socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。我的理解就是Socket就是该模式的一个实现,socket即是一种特殊的文件。
其在linux和windows环境下的头文件主要是:#include和#include
下面较为详细的介绍各个函数的使用方法,及返回值判断和处理。另外,若想对函数调用后内核的详细动作过程,可参考UNIX网络编程第一卷或TCPIP详解第二卷。
1. socket
intsocket(int domain,int type, int protocol)
_________________________返回值:非负描述符 – 成功,-1 - 出错
其中:
family指明了协议族/域,通常AF_INET、AF_INET6、AF_LOCAL等;
type是套接口类型,主要SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;
protocol一般取为0。成功时,返回一个小的非负整数值,与文件描述符类似。
对于windows环境下,在调用该函数之前需首先调用WSAStartup函数完成对Winsock服务的初始化,如
#include
WSADATA wdata;
if ( WSAStartup(MAKEWORD(2,2), &wdata) !=0 ){
return INVALID_SOCKET;
}
后面即可调用socket函数,参数意义与linux环境一致。
2. bind
intbind(int sockfd,const struct sockaddr* myaddr,socklen_t addrlen)
_________________________返回值:0 – 成功,-1 - 出错
当socket函数返回一个描述符时,只是存在于其协议族的空间中,并没有分配一个具体的协议地址(这里指IPv4/IPv6和端口号的组合),bind函数可以将一组固定的地址绑定到sockfd上。
其中:
sockfd是socket函数返回的描述符;
myaddr指定了想要绑定的IP和端口号,均要使用网络字节序-即大端模式;
addrlen是前面struct sockaddr(与sockaddr_in等价)的长度。
为了统一地址结构的表示方法,统一接口函数,使得不同的地址结构可以被bind()、connect()、recvfrom()、sendto()等函数调用。但一般的编程中并不直接对此数据结构进行操作,而使用另一个与之等价的数据结构sockaddr_in。
通常服务器在启动的时候都会绑定一个众所周知的协议地址,用于提供服务,客户就可以通过它来接连服务器;而客户端可以指定IP或端口也可以都不指定,未分配则系统自动分配。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。
Windows下的版本:
Int bind( IN SOCKET s, IN const struct sockaddr FAR * name, IN int namelen);
3. listen
intlisten(int sockfd,int backlog)
______________________返回值:0 – 成功,-1 - 出错
两个队列之和数量不得超过backlog.
4. connect
intconnect(int sockfd,conststruct sockaddr *addr, socklen_t addrlen)
______________________返回值:0 – 成功,-1 - 出错
通过此函数建立于TCP服务器的连接,实际是发起三次握手过程,仅在连接成功或失败后返回。参数sockfd是本地描述符,addr为服务器地址,addrlen是socket地址长度。
UDP的connect函数,结果与tcp调用不相同,没有三次握手过程。内核只是记录对方的ip和端口号,他们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
5. accept
intaccept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
______________________返回值:非负描述符 – 成功,-1 - 出错
(截图来自:《UNIX网络编程第一卷》)
6. send
ssize_tsend(int sockfd,constvoid *buf, size_t len,int flags)
返回值:
>0 – 成功拷贝至发送缓冲区的字节数(可能小于len),
-1 – 出错,并置错误号errno.
Windows版本:
int send(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags)
失败时返回 -1/SOCKET_ERROR
其中:
sockfd:发送端套接字描述符(非监听描述符)
buf:应用要发送数据的缓存
len:实际要发送的数据长度
flag:一般设置为0
每个TCP套接口都有一个发送缓冲区,它的大小可以用SO_SNDBUF这个选项来改变。调用send函数的过程,实际是内核将用户数据拷贝至TCP套接口的发送缓冲区的过程:若len大于发送缓冲区大小,则返回-1;否则,查看缓冲区剩余空间是否容纳得下要发送的len长度,若不够,则拷贝一部分,并返回拷贝长度(指的是非阻塞send,若为阻塞send,则一定等待所有数据拷贝至缓冲区才返回,因此阻塞send返回值必定与len相等);若缓冲区满,则等待发送,有剩余空间后拷贝至缓冲区;若在拷贝过程出现错误,则返回-1。关于错误的原因,查看errno的值。
如果send在等待协议发送数据时出现网络断开的情况,则会返回-1。注意:send成功返回并不代表对方已接收到数据,如果后续的协议传输过程中出现网络错误,下一个send便会返回-1发送错误。TCP给对方的数据必须在对方给予确认时,方可删除发送缓冲区的数据。否则,会一直缓存在缓冲区直至发送成功(TCP可靠数据传输决定的)。
7. sendto
ssize_t sendto(int sockfd,const void *buf, size_t len, int flags,
const struct sockaddr *dst_addr, socklen_t addrlen);
windows版本:
int sendto(IN SOCKET s, IN const char FAR * buf, IN int len, IN int flags, IN const struct sockaddr FAR * to, IN int tolen)
通常用于UDP套接口,用数据报方式进行数据传输。由于无连接的数据报模式下,没有建立连接,需指明目的地址,addrlen通常为sizeof(sockaddr)的长度。成功时返回发送的字节数,失败返回-1。
当本地与不同目的地址通信时,只需指定目的地址,可使用同一个UDP套接口描述符sockfd,而TCP要预先建立连接,每个连接都会产生不同的套接口描述符,体现在:客户端要使用不同的fd进行connect,服务端每次accept产生不同的fd。
因为UDP没有真正的发送缓冲区,因为是不可靠连接,不必保存应用进程的数据拷贝,应用进程中的数据在沿协议栈向下传递时,以某种形式拷贝到内核缓冲区,当数据链路层把数据传出后就把内核缓冲区中数据拷贝删除。因此它不需要一个发送缓冲区。写UDP套接口的sendto/write返回表示应用程序的数据或数据分片已经进入链路层的输出队列,如果输出队列没有足够的空间存放数据,将返回错误ENOBUFS.
8. recv
ssize_t recv(int sockfd,void *buf, size_t len,int flags)
其中:
sockfd:接收端套接字描述符;
buf:指定缓冲区地址,用于存储接收数据;
len:指定的用于接收数据的缓冲区长度;
flags:一般指定为0
表示从接收缓冲区拷贝数据。成功时,返回拷贝的字节数,失败返回-1。阻塞模式下,recv/recvfrom将会阻塞到缓冲区里至少有一个字节(TCP)/至少有一个完整的UDP数据报才返回,没有数据时处于休眠状态。若非阻塞,则立即返回,有数据则返回拷贝的数据大小,否则返回错误-1,置错误码为EWOULDBLOCK。
9. recvfrom
ssize_t recvfrom(int sockfd,void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen)
windows版本:
int recvfrom(IN SOCKET s, OUT char FAR * buf, IN int len, IN int flags, OUT struct sockaddr FAR * from,IN OUT int FAR * fromlen)
着重强调参数:
sockfd:接收端套接字描述
buf:用于接收数据的应用缓冲区地址
len:指名缓冲区大小
flags:通常为0
src_addr:数据来源端的地址
addrlen:src_addr地址的长度
注意后两个参数是输出参数,其中addrlen既是输入又是输出参数,即值-结果参数,需要在调用时,指明src_addr的长度。另外,如果不关心数据发送端的地址,可以将后两者均设置为NULL。
10. close
close缺省功能是将套接字作“已关闭”标记,并立即返回到调用进程,该套接字描述符不能再为该进程所用:即不能作为read和write(send和recv)的参数,但是TCP将试着发送发送缓冲区内已排队待发的数据,然后按正常的TCP连接终止序列进行操作(断开连接4次握手-以FIN为首的4个TCP分节)。
11. shutdown
shutdown不仅可以灵活控制关闭连接的读、写或读写功能,而且会立即执行相应的断开动作(发送终止连接的FIN分节等),此时不论有多少进程共享此套接字描述符,都将不能再进行收发数据。