4、TCP通信

一、TCP的概述

1、 面向连接的流式协议;可靠、出错重传、且每收到一个数据都要给出相应的确认.

2、 通信之前需要建立链接

3、 服务器被动链接,客户端是主动链接

1、TCP和UDP的区别:

2、TCP连接示意图:

3、建立socket连接:

二、客户端工作内容:

客户端需要进行的工作:

注意:

UDP通信收发函数:

发送函数---sendto()

接收函数---recvfrom()

TCP通信收发函数:

发送函数---send()

接收函数---recv()

1、connect函数

#include <sys/socket.h>
int connect(int sockfd,
const struct sockaddr *addr,
socklen_t len);
功能:
主动跟服务器建立链接
参数:
sockfd: socket 套接字
addr: 连接的服务器地址结构
len: 地址结构体长度
返回值:
成功: 0 失败:其他

2、send函数:

#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
功能:
用于发送数据
参数:
sockfd: 已建立连接的套接字
buf: 发送数据的地址
nbytes: 发送缓数据的大小(以字节为单位)
flags: 套接字标志(常为 0--阻塞)
返回值:
成功发送的字节数

3、recv函数

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
功能:
用于接收网络数据
参数:
sockfd:套接字
buf: 接收网络数据的缓冲区的地址
nbytes: 接收缓冲区的大小(以字节为单位)
flags: 套接字标志(常为 0--阻塞)
返回值:
成功接收到字节数
#include <stdlib.h>
int atoi(const char *str)
参数
str -- 要转换为整数的字符串。
返回值
该函数返回转换后的长整数,如果没有执行有效的转换,则返回零。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

//./a.out  192.168.2.17   9000        
//可变传参
int main(int argv,char *argc[])
{
   int fd;
   int ret;
   int cout=0;
   char rev_buf[128]="";
   struct sockaddr_in ser_addr;
   fd= socket(AF_INET,SOCK_STREAM,0);
    printf("socket_fd-->%d\n",fd);
    ser_addr.sin_family=AF_INET;

    ser_addr.sin_port=htons(atoi(argc[2]));
    ser_addr.sin_addr.s_addr=inet_addr(argc[1]);

    ret=connect(fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
    if(ret==0)
    {
        printf("连接成功\n");
    }
    while(1)
    {
            cout=send(fd,"hello zz2302",strlen("hello zz2302"),0);
        if(cout>0)
        {
            printf("send_data num-->%d\n",cout);
            cout=0;
        }
        cout=recv(fd,rev_buf,128,0);
        if(cout>0)
        {
            printf("rev_data :%s\n",rev_buf);
        }
    }
    //接受到特定字符--quit  退出当前
    // strcmp(rev_buf,"quit")
    // break
    return 0;
}

三、TCP 服务器工作内容

1、 具备一个可以确知的地址

2、 让操作系统知道是一个服务器,而不是客户端

3、 等待连接的到来

对于面向连接的 TCP 协议来说,连接的建立才真正意味着数据通信的开始

1、bind函数:

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
功能:将当前进程和IP进行绑定
(1)参数 sockfd ,需要绑定的socket。
(2)参数 addr ,存放了服务端用于通信的地址和端口。
(3)参数 addrlen ,表示 addr 结构体的大小
(4)返回值:成功则返回0 ,失败返回-1,错误原因存于 errno 中。如果绑定的地址错误,或者端口已被占用,bind 函数一定会报错,否则一般不会返回错误。

2、listen函数

#include <sys/socket.h>
int listen(int sockfd, int backlog);
功能:
将套接字由主动修改为被动.
使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接--连接的个数
参数:
sockfd: socket 监听套接字
backlog:连接队列的长度--连接的客户端数量
返回值:
成功:返回 0
失败:其他

3、accept函数:

#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);
功能:
从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)
参数:
sockfd: socket 监听套接字
cliaddr: 用于存放客户端套接字地址结构
addrlen:套接字地址结构体长度的地址
返回值:
已连接套接字
返回的是一个已连接套接字,这个套接字代表当前这个连接
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>

//./a.out  192.168.2.17   9000        
//可变传参
int main(int argv,char *argc[])
{
   int fd;
   int ret;
   int cout=0;
   char rev_buf[128]="";
   struct sockaddr_in ser_addr;
   struct sockaddr_in cli_addr;

   fd= socket(AF_INET,SOCK_STREAM,0);
    printf("socket_fd-->%d\n",fd);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_port=htons(atoi(argc[2]));
    ser_addr.sin_addr.s_addr=inet_addr(argc[1]);
   //bind函数和客户端中的connect函数类似
    ret=bind(fd,(struct sockaddr *)&ser_addr,sizeof(ser_addr));
    if(ret==0)
    {
        printf("绑定成功\n");
    }
   ret= listen(fd,10);
   if(ret==0)
   {
    printf("listen ok\n");
   }
   int cli_len=sizeof(cli_addr);
   int cli_fd=0;

   cli_fd=accept(fd,(struct sockaddr *)&cli_addr,&cli_len);
   printf("cli_fd-->%d\n",cli_fd);
    while(1)
    {
        recv(cli_fd,rev_buf,128,0);
        printf("rev_buf-->%s\n",rev_buf);

        send(cli_fd,"OK",strlen("OK"),0);
    }  
    close(fd);
    return 0;
}

四、三次握手和四次挥手示意图

三次握手为什么不是两次????

四次挥手,三次可以吗,五次可以吗????

1、三次握手

通过connect函数,由客户端发起:

ACK:确认序号有效。
FIN:释放一个连接。--断开连接
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。(发送请求信号)
URG:紧急指针(urgent pointer)有效。
第一次握手:客户端要向服务端发起连接请求,首先客户端随机生成一个起始序列号ISN(比如是100),那客户端向服务端发送的报文段包含SYN标志位(也就是SYN=1),序列号seq=100。
第二次握手:服务端收到客户端发过来的报文后,发现SYN=1,知道这是一个连接请求,于是将客户端的起始序列号100存起来,并且随机生成一个服务端的起始序列号(比如是300)。然后给客户端回复一段报文,回复报文包含SYN和ACK标志(也就是SYN=1,ACK=1)、序列号seq=300、确认号ack=101(客户端发过来的序列号+1)。
第三次握手:客户端收到服务端的回复后发现ACK=1并且ack=101,于是知道服务端已经收到了序列号为100的那段报文;同时发现SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号300给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=301(服务端序列号+1)、seq=101(第一次握手时发送报文是占据一个序列号的,所以这次seq就从101开始,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是101)。当服务端收到报文后发现ACK=1并且ack=301,就知道客户端收到序列号为300的报文了,就这样客户端和服务端通过TCP建立了连接。

三次握手的本质是确认通信双方收发数据的能力
首先,我让信使运输一份信件给对方,对方收到了,那么他就知道了我的发件能力和他的收件能力是可以的。
于是他给我回信,我若收到了,我便知我的发件能力和他的收件能力是可以的,并且他的发件能力和我的收件能力是可以。
然而此时他还不知道他的发件能力和我的收件能力到底可不可以,于是我最后回馈一次,他若收到了,他便清楚了他的发件能力和我的收件能力是可以的。
ACK:确认序号有效。
FIN:释放一个连接。
PSH:接收方应该尽快将这个报文交给应用层。
RST:重置连接。
SYN:发起一个新连接。
URG:紧急指针(urgent pointer)有效。

2、四次挥手

close 关闭套接字

比如客户端初始化的序列号ISA=100,服务端初始化的序列号ISA=300。TCP连接成功后客户端总共发送了1000个字节的数据,服务端在客户端发FIN报文前总共回复了2000个字节的数据。
第一次挥手:当客户端的数据都传输完成后,客户端向服务端发出连接释放报文(当然数据没发完时也可以发送连接释放报文并停止发送数据),释放连接报文包含FIN标志位(FIN=1)、序列号seq=1101(100+1+1000,其中的1是建立连接时占的一个序列号)。需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。
第二次挥手:服务端收到客户端发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=1102(客户端FIN报文序列号1101+1)、序列号seq=2300(300+2000)。此时服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。
第三次挥手:服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=1102、序列号seq=2350(2300+50)。
第四次挥手:客户端收到服务端发的FIN报文后,向服务端发出确认报文,确认报文包含ACK标志位(ACK=1)、确认号ack=2351、序列号seq=1102。注意客户端发出确认报文后不是立马释放TCP连接,而是要经过2MSL(最长报文段寿命的2倍时长)后才释放TCP连接。而服务端一旦收到客户端发出的确认报文就会立马释放TCP连接,所以服务端结束TCP连接的时间要比客户端早一些。

使用抓包工具解析数据:

目前我们选择有线连接

过滤器设置:

ip.addr == 192.168.31.205 and tcp.port==8089

数据报分析:

数据链路层:

网络层分析:

传输层内容:

传输数据:

四次挥手数据:

五、端口复用

出现以下问题,就是因为端口被占用了

如果出现以上情况,但是你也不想使用代码去解决,等三分钟自动好了。

注意:在同一个主机中,如何IP地址相同,那么端口就必须有所区分

最起码得保证IP和端口有一个不一样。

定义部分:

setsockopt()函数在使用的时候,必须在bind绑定之前被调用。

int setsockopt(int sockFd, int level, int optname, 
const void *optval, socklen_t optlen);
1、sockfd:将要被设置或者获取选项的套接字。
2、level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。一般设成SOL_SOCKET以存取socket层。
SOL_SOCKET:通用套接字选项.
IPPROTO_IP:IP选项.IPv4套接口
IPPROTO_TCP:TCP选项.
IPPROTO_IPV6: IPv6套接口
3、optname: 欲设置的选项,有下列几种数值

4、optval: 对于setsockopt(),指针,指向存放选项待设置的新值的缓冲区。
获得或者是设置套接字选项.根据选项名称的数据类型进行转换。
5、optlen:optval缓冲区长度。

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值