建立一个 TCP 连接时会发生下述情形:
1. 服务器必须准备好接受外来的连接。这通常通过调用 socket、bind 和 listen 这三个函数来完成,我们称之为被动打开。
2. 客户通过调用 connect 发起主动打开,这导致客户TCP发送一个SYN(同步)分节,标识希望连接的服务器端口以及初始序号。通常SYN分节不携带数据,其所在IP数据报只含有一个IP首部、一个TCP首部及可能有的TCP选项。
3. 服务器发送回一个包含服务器初始序号以及对客户端 SYN 段确认的 SYN + ACK 段作为应答,由于一个 SYN 占用一个序号,因此确认序号设置为客户端初始序号加 1。
4. 客户端发送确认序号为服务器初始序号加 1 的 ACK 段,对服务器 SYN 段进行确认。
这种交换至少需要三个分组,因此称之为TCP的三路握手。
一旦TCP建立连接,客户/服务器之间便可以进行数据通信。
1. 服务器端
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<netinet/in.h>
#include <errno.h>
#define PORT 6666
int main(int argc,char **argv)
{
int ser_sockfd,cli_sockfd;
int err,n;
int addlen;
struct sockaddr_in ser_addr;
struct sockaddr_in cli_addr;
char recvline[200],sendline[200];
ser_sockfd = socket(AF_INET,SOCK_STREAM,0); //创建套接字
if(ser_sockfd == -1)
{
printf("socket error:%s
",strerror(errno));
return -1;
}
bzero(&ser_addr,sizeof(ser_addr));
/*在待捆绑到该TCP套接口(sockfd)的网际套接口地址结构中填入通配地址(INADDR_ANY)
和服务器的众所周知端口(PORT,这里为6666),这里捆绑通配地址是在告知系统:要是系统是
多宿主机(具有多个网络连接的主机),我们将接受宿地址为任何本地接口的地址*/
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_port = htons(PORT);
//将网际套接口地址结构捆绑到该套接口
err = bind(ser_sockfd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
if(err == -1)
{
printf("bind error:%s
",strerror(errno));
return -1;
}
//将套接口转换为一个监听套接口,监听等待来自客户端的连接请求
err = listen(ser_sockfd,5);
if(err == -1)
{
printf("listen error
");
return -1;
}
printf("listen the port:
");
while(1)
{
addlen = sizeof(struct sockaddr);
//等待阻塞,等待客户端申请,并接受客户端的连接请求
//accept成功,将创建一个新的套接字,并为这个新的套接字分配一个套接字描述符
cli_sockfd = accept(ser_sockfd,(struct sockaddr *)&cli_addr,&addlen);
if(cli_sockfd == -1)
{
printf("accept error
");
}
//数据传输
while(1)
{
printf("waiting for client...
");
n = recv(cli_sockfd,recvline,1024,0);
if(n == -1)
{
printf("recv error
");
}
recvline[n] = '