基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。以下是基于 TCP 套接字编程的流程图:
一些常用的结构如下:
套接字的普通C定义通用的地址结构:
struct sockaddr
{
u_charsa_len;//长度
u_short sa_family;//协议
char sa_data[14];//数据
};
IP专用的地址结构:
struct sockaddr_in
{
u_char sin_len; //长度
u_short sin_family; //协议
u_short sin_port; //端口
structin_addr sin_addr; //ip地址
char sin_zero[8]; //数据
};
struct in_addr
{
u_longs_addr;
};
各个函数的函数原型如下:
1.socket()
套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。任何套接字编程都必须调用socket 函数获得套接字描述符,这样才能对套接字进行操作。以下是该函数的描述:
/* 套接字 */
/*
* 函数功能:创建套接字描述符;
* 返回值:若成功则返回套接字非负描述符,若出错返回-1;
* 函数原型:
*/
#include <sys/socket.h>
int socket(int family, int type, int protocol);
/*
* 说明:
* socket
类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;
* family
表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:
* (1)AF_INET IPv4因特网域
* (2)AF_INET6 IPv6因特网域
* (3)AF_UNIX Unix域
* (4)AF_ROUTE 路由套接字
* (5)AF_KEY 密钥套接字
* (6)AF_UNSPEC 未指定
* type确定socket的类型,常用类型如下:
* (1)SOCK_STREAM 有序、可靠、双向的面向连接字节流套接字
* (2)SOCK_DGRAM 长度固定的、无连接的不可靠数据报套接字
* (3)SOCK_RAW 原始套接字
* (4)SOCK_SEQPACKET 长度固定、有序、可靠的面向连接的有序分组套接字
* protocol指定协议,常用取值如下:
* (1)0 选择type类型对应的默认协议
* (2)IPPROTO_TCP TCP传输协议
* (3)IPPROTO_UDP UDP传输协议
* (4)IPPROTO_SCTP SCTP传输协议
* (5)IPPROTO_TIPC TIPC传输协议
*
*/
2.connect()
在处理面向连接的网络服务时,例如 TCP ,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。TCP 客户端可以调用函数connect 来建立与 TCP 服务器端的一个连接。
/*
* 函数功能:建立连接,即客户端使用该函数来建立与服务器的连接;
* 返回值:若成功则返回0,出错则返回-1;
* 函数原型:
*/
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
/*
* 说明:
* sockfd是系统调用的套接字描述符,即由socket函数返回的套接字描述符;
* servaddr是目的套接字的地址,该套接字地址结构必须包含目的IP地址和目的端口号,即想与之通信的服务器地址;
* addrlen是目的套接字地址的大小;
*
* 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址,即内核会确定源IP地址,并选择一个临时端口号作为源端口号;
*/
3.bind()
调用函数 socket 创建套接字描述符时,该套接字描述符是存储在它的协议族空间中,没有具体的地址,要使它与一个地址相关联,可以调用函数bind 使其与地址绑定。客户端的套接字关联的地址一般可由系统默认分配,因此不需要指定具体的地址。若要为服务器端套接字绑定地址,可以通过调用函数 bind 将套接字绑定到一个地址。
/* 套接字的基本操作 */
/*
* 函数功能:将协议地址绑定到一个套接字;其中协议地址包含IP地址和端口号;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*
* 说明:
* sockfd 为套接字描述符;
* addr是一个指向特定协议地址结构的指针;
* addrlen是地址结构的长度;
*/
4.listen()
在编写服务器程序时需要使用监听函数 listen 。服务器进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。
/*
* 函数功能:接收连接请求;
* 函数原型:
*/
#include <sys/socket.h>
int listen(int sockfd, int backlog);//若成功则返回0,若出错则返回-1;
/*
* sockfd是套接字描述符;
* backlog是该进程所要入队请求的最大请求数量;
*/
5.accept()
accept 函数由 TCP 服务器调用,用于从已完成连接队列队头返回下一个已完成连接。如果已完成连接队列为空,那么进程被投入睡眠。该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次握手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。
/* 函数功能:从已完成连接队列队头返回下一个已完成连接;若已完成连接队列为空,则进程进入睡眠;
* 函数原型:
*/
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
//返回值:若成功返回套接字描述符,出错返回-1;
/*
* 说明:
* 参数 cliaddr 和 addrlen 用来返回已连接的对端(客户端)的协议地址;
*
* 该函数返回套接字描述符,该描述符连接到调用connect函数的客户端;
* 这个新的套接字描述符和原始的套接字描述符sockfd具有相同的套接字类型和地址族,而传给accept函数的套接字描述符sockfd没有关联到这个链接,
* 而是继续保持可用状态并接受其他连接请求;
* 若不关心客户端协议地址,可将cliaddr和addrlen参数设置为NULL,否则,在调用accept之前,应将参数cliaddr设为足够大的缓冲区来存放地址,
* 并且将addrlen设为指向代表这个缓冲区大小的整数指针;
* accept函数返回时,会在缓冲区填充客户端的地址并更新addrlen所指向的整数为该地址的实际大小;
*
* 若没有连接请求等待处理,accept会阻塞直到一个请求到来;
*/
6.send(),recv();
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据,客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。客户或者服务器应用程序都用recv函数从TCP连接的另一端接收数据。
int send( SOCKET s, const char FAR *buf, int len, int flags );
/*
该函数的第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
*/
int recv( SOCKET s, char FAR *buf, int len, int flags);
/*
该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
*/
/*
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,
调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止,
如果recv函数在等待协议接收数据时网络断开了,
那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
*/
7.read(),write();
Ssize_t write(int fd,const void *buf,size_t nbytes);
Ssize_t read(int fd,void *buf,size_t nbyte)
/*
通常情况下 write和send read和recv 作用差不许多
*/
7.close()和 shutdown()
/* 函数功能:关闭套接字,若是在 TCP 协议中,并终止 TCP 连接;
* 返回值:若成功则返回0,若出错则返回-1;
* 函数原型:
*/
#include <unistd.h>
int close(int sockfd);
/*
* 函数功能:关闭套接字上的输入或输出;
* 返回值:若成功则返回0,若出错返回-1;
* 函数原型:
*/
#include <sys/socket.h>
int shutdown(int sockfd, int how);
/*
* 说明:
* sockfd表示待操作的套接字描述符;
* how表示具体操作,取值如下:
* (1)SHUT_RD 关闭读端,即不能接收数据
* (2)SHUT_WR 关闭写端,即不能发送数据
* (3)SHUT_RDWR 关闭读、写端,即不能发送和接收数据
*
*/