一、TCP编程流程
服务器:
1、Socket
函数原型:
int socket(int domain, int type , int protocol);
domain参数告诉系统应该使用哪个底层协议族。
Type:用于指定新套接字的通信属性,它有两个选项,一个是SOCK_STREAM,它表示有序、可靠、面向连接的双向字节流。对AF_INET域,通常是由TCP连接提供的。还有一种是SOCK_DGRAM,我们可以用它来发送最大长度固定的消息,但是消息是否会被正确传递或者说消息是否真的能不乱序准确到达,并不保证。
protocol:通常所用协议一般由套接字类型和套接字域来决定,通常这个选项不需要选择,将它置为零表示使用默认协议。
套接字:为了区别不同的应用程序进程和连接,许多计算机系统为应用程序与TCP/IP协议提供了称为套接字的接口,区分不同应用程序进程间的网络通信和连接。
2、bind
这个可以看成是绑定函数,我们创建完套接字之后,在我们的服务器端,我们需要完成套接字和主机IP还有端口号的绑定,这样服务器在连接的时候才知道要往哪个IP地址的主机上连接哪个进程,向哪个进程请求服务,所以我们服务器端的提供特定服务的进程它的端口号一般是不会变的。
函数原型:
int bind(int socket, const sockaddr *address, size_t address_len);
使用这个函数我们可以完成我们的函数绑定的功能,但是我们IPV4中一般使用的是sockaddr_in这个结构体填写地址信息,所以我们调用这个函数的时候还需要转化一次,address_len指的是我们传进来的结构体的大小,相信分析到这里大家也知道为什么需要这个长度信息了,我们在调用函数的时候地址发生了一次类型的强转,我们要给出它的有效长度才知道,我们地址绑定的时候信息能够读到哪。
bind函数在成功的时候返回0,失败的时候返回-1。
3、listen
它是我们的监听函数,我们创建出套接字并为它命名之后,还不能马上使用,还需要调用listen函数为它创建两个队列,一个是正在建立连接的队列,也就是正在做三次握手工作的队列;另一个是;已经完成连接的队列,调用完listen之后我们的普通socket才转化为监听socket。
函数原型:
int listen(int socket, int backlog);
Socket表示我们要将那个套接字转化为监听套接字;
backlog参数代表处于完成三次握手的socket的上限;
4、accept
Int accept(int socket, struct sockaddr *address, size_t *address_len)
socket表示我们已经创建的监听套接字,这里的addrss表示的是我们要传入一个地址结构体,传入之后,当我们的accept选中一个已经处于完全连接状态的套接字,将它的请求方地址写入这个结构体中,方便我们的服务器发送给它消息,*address_t表示我们传入的结构体长度的指针变量,同时它还可以带出来,我们填入客户端的信息后,它实际使用的字节长度。
accept系统调用只有当有客户程序试图链接到由socket参数指定的套接字上时才返回。这里客户是指,在套接字对立中排在第一个的未处理连接。accept函数将创建一个新套接字来与该客户进行通信,并且返回新套接字的描述符。新套接字的类型和服务器监听套接字类型是一样的。
如果套接字队列中没有未处理的连接,accept将阻塞(程序将暂停)知道有客户建立连接为止。我们可以通过对套接字文件描述符设置O_NONBLOCK标识来改变着一行为,使用的函数是fcntl。
Int flags=fcntl(socket, F_GETFL, 0);
fcntl(socket, F_SETFL, O_NONBLOCK|flags);
注意我们先用一个flag去保存它原来的状态,我们只是想设置为O_NONBLOCK,并没有想要修改它原来设置的一些东西。
当有未处理的客户连接时,accept函数返回一个新的套接字文件描述符。发生错误时函数将返回-1。可能的错误情况大部分与bind、listen调用类似,其他的错误有EWOULDBLOCK和EINTR。前者是当指定了O_NOBLOCK标识,但队列中没有未处理连接时产生错误(不阻塞,立即返回)。后者是当进程阻塞在accept调用时,执行被中断而产生的错误。
5、recv/send
recv函数原型:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
我们这个recv函数会阻塞接受,会一直等待我们的客户端发送消息,除非客户端主动关闭,否则,我们服务器的客户端没有什么问题的话会一直阻塞。
Send函数原型:
ssize_t send(int sockfd, const void *buf , size_t len, int flags);
6、close
这里close和我们平常关闭文件是一样的,这里就不做过多介绍了。
客户端:
客户端这边的用法和服务器基本一致,大家可以仿照服务器的写法,来写这个客户端的代码,我们只说一个不同的connect。
1、Socket
2、Listen
3、connect
int connect (int sockfd, const struct sockaddr *serv_addr , socklen_t addrlen);
Connect中sockfd是通过socket函数的返回值获得的,serv_addr参数是服务武器监听的socket地址,addrlen参数则指定这个地址的长度。
Connect成功的时候返回0。一旦成功建立连接,sockfd就唯一地标识了这个连接,客户端就可以通过读写sockfd来与服务器通信。Connetct失败返回-1。
4、recv/send
5、close
下面我们来编程操作熟悉一下!
服务器端代码:
客户端: