网络socket编程相关函数
socket函数
socket函数为系统创建一个套接字,应用程序通过访问该函数创建的套接字实现对数据的发送和接收。
函数原型如下:如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain, int type, int protocol);
- 参数domain:函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
domain | 含义 |
---|---|
AF_INET | IPV4 Internet协议 |
AF_INET6 | IPV4 Internet协议 |
AF_UNIX | 同一主机进程间通信 |
- 参数type:用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
type类型 | 含义 |
---|---|
SOCK_STREAM | Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输 |
SOCK_DGRAM | 支持UDP连接(无连接状态的消息) |
SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 |
SOCK_RAW | RAW类型,提供原始网络协议访问 |
SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
SOCK_PACKET | 这是一个专用类型,不能呢过在通用程序中使用 |
并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
参数protocol:用于制定某个协议的特定类型,通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
返回值 errno:函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
值 | 含义 |
---|---|
EACCES | 没有权限建立制定的domain的type的socket |
EAFNOSUPPORT | 不支持所给的地址类型 |
EINVAL | 不支持此协议或者协议不可用 |
EMFILE | 进程文件表溢出 |
ENFILE | 已经达到系统允许打开的文件数量,打开文件过多 |
ENOBUFS/ENOMEM | 内存不足。socket只有到资源足够或者有进程释放内存 |
EPROTONOSUPPORT | 制定的协议type在domain中不存在 |
实例:创建一个流式套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
bind函数
bind函数用于将套接字与网络地址绑定,一般适用于未连接的数据报或流类套接口,在connect()或listen()调用前使用。
其原型如下:如果成功返回0;如果出错返回-1
#include <sys/types.h>
#include <sys/socket.h>
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen);
- 参数sockfd为socket函数创建的套接字
- 参数my_addr为sockaddr * 类型的网络地址结构体
sockaddr定义如下:
struct sockaddr{
u_short sa_family;
char sa_data[14];
};
- 参数addrlen为网络地址的长度
listen函数
listen函数仅用在服务端程序,用来监听套接字的状态。
其原型如下:
#include <sys/socket.h>
int listen(int sock_fd, int backlog);
- 参数sock_fd为将要监听的套接字
- 参数 backlog 指定同时能处理的最大连接要求, 如果连接数目达此上限则client 端将收到ECONNREFUSED 的错误.
- 返回值:成功则返回0, 失败返回-1, 错误原因存于errno
错误信息 | 含义 |
---|---|
EBADF | 参数sockfd 非合法socket 处理代码 |
EACCESS | EACCESS |
EOPNOTSUPP | 指定的socket 并未支援listen 模式. |
select函数
select函数是sock网络编程中比较重要的一个函数之一,其主要作用是多路复用。举个例子:
read(sock_fd1,buff,sizeof(buff));
read(sock_fd2,buff,sizeof(buff));
我们知道read ,recv等函数都是阻塞函数,在sock程序中,如果有两个套接字sock_fd1和sock_fd2,使用read或者recv函数读取套接字的内容,由于他们都是阻塞函数,使用read函数或recv函数读取sock_fd1的内容,此时程序会一直阻塞,如果这个时候sock_fd2有内容来了是没有办法立刻读取的,必须等read函数返回才能读取。这个时候便可以使用select函数来解决这个问题。
其原型为:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
- maxfdp为最大套接字加1,
fd_set实际上是一个long类型的数组类型,表示文件描述符集,即多个套接字的集合。对于fd_set我们需要了解用来操作fd_set结构的几个宏
fd_set set;- FD_ZERO(&set); /将set清零使集合中不含任何fd/
- FD_SET(sock_fd, &set); /将sock_fdfd加入set集合/
- FD_CLR(sock_fd, &set); /将sock_fd从set集合中清除/
- FD_ISSET(sock_fd, &set); /在调用select()函数后,用FD_ISSET来检测sock_fd是否在set集合中,当检测到fsock_fdd在set中则返回真,否则,返回假(0)/
fd_set *readfds是指向fd_set结构的指针,select函数监视该文件描述符集和中的套接字 是否发生读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。
- fd_set *writefds是指向fd_set结构的指针,elect函数监视该文件描述符集和中的套接字 是否发生可写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。一般设该值为NULL,表示不关心任何文件的写变化。
- fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常文件。
- timeval 是一个常用的结构体,有两个成员秒和毫秒。用来表示阻塞时间。
struct timeval
{
long tv_sec; //second
long tv_usec; //microsecond
};
现在我们用select函数多路复用来解决上面阻塞的问题,假如已经有了sock_fd1和sock_fd2
fd_set rfds;
struct timeval tv;
/*将套接字sock_fd1和sock_fd2添加到文件描述符集rfds*/
FD_SET(sock_fd1,&rfds);
FD_SET(sock_fd2,&rfds);
/*设置阻塞时间为1s*/
tv.tv_sec = 1;
tv.tv_usev = 0;
/*去sock_fd1和sock_fd2中的最大值*/
maxfd = sock_fd1 > sock_fd2? sock_fd1:sock_fd2;
/*调用select函数,如果sock_fd1或sock_fd2任何一个套接字发生可读变化select函数就会返回,否则达到阻塞时间1S后返回*/
select(maxfd+1,&rfds,NULL,NULL,&tv);
/*判断具体是哪一个套接字发生变化,如果sock_fd1发生可读变化*/
if(FD_ISSET(sock_fd1,&rfds))
{
/*读出sock_fd1的值,注意因为现在是在套接字发生可读变化的情况下才会调用read函数,所以不会再有不必要的阻塞*/
read(sock_fd1,buff,sizeof(buff));
}
/*如果sock_fd2发生可读变化*/
if(FD_ISSET(sock_fd2,&rfds))
{
read(sock_fd2,buff,sizeof(buff));
}
send函数
send函数用来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。注意:send函数仅仅是把buf中的数据copy到sock_fd的发送缓冲区的剩余空间里,数据的传送是靠网络协议传输。
其原型为:
int send( SOCKET sock_fd,char *buf,int len,int flags );
- 参数sock_fd:指定发送端套接字
- 参数buf:包含待发送数据的缓冲区。
- 参数len:缓冲区中数据的长度。
- 参数flags:调用执行方式。一般设该值为0。
- 返回值:若无错误发生,send()返回所发送数据的总数(请注意这个数字可能小于len中所规定的大小)。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码
调用该函数时,send先比较待发送数据的长度len和套接字sock_fd的发送缓冲的长度(因为待发送数据是要copy到套接字sock_fd的发送缓冲区的):
1. 如果len大于sock_fd的发送缓冲区的长度,该函数返回SOCKET_ERROR;
2. 如果len小于或者等于sock_fd的发送缓冲区的长度,那么send先检查协议是否正在发送sock_fd的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sock_fd的发送缓冲中的数据或者sock_fd的发送缓冲中没有数据,那么 send就比较sock_fd的发送缓冲区的剩余空间和len:
1. 如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完;
2. 如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里。
3. 如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)
recv函数
recv函数用于从TCP连接的另一端接收数据。
其原型为:
int recv( SOCKET sock_fd, char *buf, int len, int flags)
- 参数 sock_fd:指定接收端套接字;
- 参数buf:指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
- 参数 len:指明buf的长度;
- 参数flag:调用执行方式,一般设该值为0
应用程序调用recv函数时,recv先等待sock_fd的发送缓冲中的数据被协议传送完毕,如果协议在传送sock_fd的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;如果sock_fd的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果sock_fd接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;当协议把数据接收完毕,recv函数就把sock_fd的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。