27TCP

TCP通信流程步骤:

服务端: 等待(被动)接收发送

1: 创建 socket:  socket()

2: 绑定端口:      bind()

3: 监听端口:      listen()

4: 接受连接:      accept()

5: 读取消息:      read()

6: 发送消息:      write()

7: 关闭套接字:  close()

客户端:主动发送接收

1: 创建 socket:   socket()

2: 连接服务端:    connect()

3: 发送数据:        write()

4: 接受结果:         read()

5: 关闭套接字:     close()

TCP通信流程图:

什么是套接字?

      简单点,就是IP地址+端口号。

注意:IP地址决定往哪个主机发送,端口决定哪个程序接受

优点:

1.像操作文件描述符一样操作套接字

2.双向通信接口,起源于管道

3.比管道功能更强,应用更广泛

4.支持 read , write 等操作用于收发数据

服务端具体函数解析:

创建套接字

#include<sys/socket.h>

int socket(int domain,  int type,  int protocol)

返回值:  套接字

参数:

domain     协议族

type          套接字类型

protocol    套接字协议

第一参数参考值:

socket 创建套接字 --- domain

PF_UNIX, PF_LOCAL       本地通信

PF_INET                      IPV4协议

PF_INET6                    IPV6协议

PF_IPX                         Novell 公司的 IPC 通信协议

PF_NETLINK               与内核间的接口

PF_X25                        ITU-T X.25 / ISO-8208

PF_AX25                     无线 AX.25 协议

PF_ATMPVC               访问原始ATM的PVC(永久虚连接)

PF_APPLETALK         苹果公司的 Appletalk 协议

PF_PACKET                底层包接口

第二参数参考值:

socket 创建套接字 --- type

SOCK_STREAM   提供面向连接的,有序的,可靠数据流 TCP

SOCK_DGRAM     支持数据报  UDP

SOCK_SEQPACKET   提供基于连接的有序的,可靠数据报通信

SOCK_RAW            对原始网络协议访问

SOCK_RDM            提供可靠的数据报层,不保证有序性

SOCK_PATET         已废弃

注意:

套接字 SOCKET 类型

SOCK_STREAM

流套接字:使用TCP协议。提供面向连接的,有序的,可靠的数据通信流。如 telnet,  http 等

SOCK_DGRAM

数据报套接字:使用 UDP 协议。 提供无连接的,无序的,不保证可靠性的数据通信流。如 tftp, bootp 等

SOCK_RAW

原始流套接字:收发原始数据包,应用于底层协议开发,进行底层操作

只有 root 用户才有权限创建这个 socket

第三参数参考值:

socket 创建套接字 --- protocol

<netinet/in.h>

IPPROTO_IP (0):   接受所有IP数据包

IPPROTO_ICMP:  ICMP 协议 (ping)

IPPROTO_TCP:   TCP 协议

IPPROTO_UDP:   UDP协议

IPPROTO_RAW: RAW 只能发送包,且需要自己填写IP头,计算校验和

绑定套接字

int  bind(int sockfd,  struct sockaddr  *myAddr,  socklen_t  addrLen)

sockfd      套接字描述符

myAddr     主机地址

addrLen     sockaddr 结构体大小

实现本机地址(协议族+IP+端口)  与 套接字绑定,收发消息即读写套接字文件描述符

sockaddr  结构体:

套接字地址 

struct sockaddr

{

    u_short  sa_family;       //协议族

    char       sa_data[14];   //协议地址

}

对于不同的协议族,协议地址 sa_data[14] 有不同的描述方式

AF_INET 协议族的协议地址

struct  sockaddr_in

{

    short                 sin_family;    /*地址族  AF_INET*/

    u_short             sin_port;        /*端口号*/

    struct in_addr   sin_addr;       /*32位IP地址*/

    char                  sin_zero[8];   /*预留*/

}

注意:一般定义sockaddr_in,填写信息,再将sockaddr_in转成sockaddr再使用在bind函数。

IP地址转换

#include<arpa/inet.h>

ulong  inet_addr(char* pAddr)

字符串 IP 转 整数 IP

int  inet_aton(char* pAddr,  struct in_addr *pInAddr)

字符串 IP 转 整数 IP

char*   inet_ntoa(struct  in_addr inAddr)

整数 IP 转 字符串 IP

字节序转换

网络字节序:  大字节序

主机字节序:

x86:  小字节序

ppc:  大字节序

主机序转网络序

ulong   htonl(ulong    host)

ushort  htons(ushort  host)

网络序转主机序

ulong   ntohl(ulong    net)

ushort  ntohs(ushort  net)

监听端口

int listen(int sockfd,  int backlog)

sockfd       监听的套接字

backlog     套接字接收的最大连接数,超出则向客户端发出 ECONNEREFUSED 错误

连接处理

int  accept(int  sockfd,  struct  sockaddr *addr,  socklen_t *addrlen)

阻塞,等待接收客户端连接申请

接收成功,则创建套接字,用于发送消息给客户端

addr       获取到客户端地址

addrlen  获取到客户端地址大小

sockfd设置为非阻塞的情况,未接收到连接申请,则返回错误

关闭套接字

1: close(sockfd)   

同文件操作

关闭时,只是将套接字访问计数器 -1, 计数器为0时真正关闭

用于创建子进程进行并发管理

2:shutdown(sockfd,   how)  按需关闭套接字

SHUT_RD (0):           关闭读功能,丢弃接收到的数据

SHUT_WR(1):           关闭写功能,不能发送数据

SHUT_RDWR(2):      彻底关闭套接字连接

服务端具体函数解析:

-----------------------创建套接字与服务端一样,参考上面--------------

int  connect(int  sockfd,  struct sockaddr *srvaddr,  int addrlen)

sockfd     创建的用于通信套接字

srvaddr    接收端地址

addrlen    地址结构体大小

发送数据

write(int sockfd, void *buf,   size_t  len)

send(int sockfd, void *msg,  int len,  int flags)

flags 标志一般填0, 特殊情况下用法如下

MSG_CONFIRM 

通知链路层,即将收到回应,链路层未收到回应,则会定期探测邻居消息 (只能用于 SOCK_DGRAM  和 SOCK_RAW)

MSG_DONTROUTE         

不通过网关发送数据,只发送到同一子网计算机

MSG_DONTWAIT

使用非阻塞操作,阻塞则返回EAGAIN错误

MSG_EOR

结束记录(SOCK_SEQPACKET 时使用)

MSG_MORE

还有后续数据要发送,效果相当于套接字使用了TCP_CORK属性。通知内核,这个帧的数据还没发完,后续数据发送后,这个数据才能发送出去。

MSG_OOB         

发送带外数据。提高优先级,先于其他数据进行发送

接收消息

read(int sockfd,  void *buf,  size_t len)

recv(int sockfd,  void *buf,  size_t len, int flags)

flags标志一般填0, 特殊情况下用法如下

MSG_DONTWAIT

非阻塞操作,阻塞则返回EAGAIN错误

MSG_OOB

接收带外数据

MSG_PEEK

只查看消息,不从缓冲区删除数据

MSG_TRUNC

返回包的真实长度(只用于流套接字)

MSG_WAITALL

等待接收到的数据长度为len后才返回

例子:

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<unistd.h>

#define SRV_PORT  8888

#define CLT_PORT  6666

void Tcp_server()

{

      int fd;

      int iRet;

      struct sockaddr_in  addr;

      socklen_t addrlen = sizeof(addr);

      //创建套接字

      fd = socket(PF_INET, SOCK_STREAM, 0);//IPPROTO_IP

      if (fd < 0)

      {

           perror("Socket failed!");

           return;

      }

      //协议、端口、ip地址等属性

      addr.sin_family = AF_INET;//use IPV4 address

      addr.sin_port   = htons(SRV_PORT);

      addr.sin_addr.s_addr = htonl(INADDR_ANY);//INADDR_ANY=0L;

      //把套接字和IP、端口号进行绑定

      iRet = bind(fd, (struct sockaddr*)&addr, addrlen);

      if (iRet)

      {

           perror("Bind failed!");

           close(fd);

           return;

      }

      //同时监听一个套接字

iRet = listen(fd, 1);

      if (iRet)

      {

           perror("Listen failed!");

           close(fd);

           return;

      }

      int clientfd;

      struct sockaddr_in srcaddr;

      char szTip[] = "Welcome!";

      char szBuf[1024]={};

      char szMsg[1024];

      //接受信息,确认连通,再创建通信套接字,包含客户端的IP和端口号

      clientfd = accept(fd,  (struct sockaddr*)&srcaddr, &addrlen);

      if (clientfd < 0)

      {

           perror("Accept failed!");

           return ;

      }

      else

      {

           printf("Connect form %s[%d] ", inet_ntoa(srcaddr.sin_addr), ntohs(srcaddr.sin_port));    

           //send(clientfd, szTip, strlen(szTip), 0);

           write(clientfd, szTip, strlen(szTip)); //send data to client

           while(1)

           {

                 //接受

                 memset(szBuf, 0, sizeof(szBuf));

                 //iRet = recv(clientfd, szBuf, 1024, 0);

                 iRet = read(clientfd, szBuf, sizeof(szBuf));

                 if (iRet < 0)

                 {

                      perror("Fail to read!");

                      break;

                 }

                 printf(" Recv: %s", szBuf);

                

                 //发送

                 fprintf(stderr, "Send:");

                 memset(szMsg, 0, sizeof(szMsg));

                 read(STDIN_FILENO, szMsg, sizeof(szMsg));

                 write(clientfd, szMsg, strlen(szMsg));

           }

      }

      close(clientfd);

      close(fd);

      return ;

}

void Tcp_client()

{

      char szDestIp[16];

      int  port;

      fprintf(stderr, "Connect to:");

      scanf("%s%d", szDestIp, &port);

      int fd;

      int iRet;

      struct sockaddr_in srvaddr;

      socklen_t addrlen = sizeof(srvaddr);

      fd = socket(PF_INET, SOCK_STREAM, 0);

      if (fd < 0)

      {

           perror("Socket failed!");

           return;

      }

      srvaddr.sin_family = AF_INET;

      srvaddr.sin_port  = htons((short)port);

      srvaddr.sin_addr.s_addr = inet_addr(szDestIp);

      iRet = connect(fd, (struct sockaddr*)&srvaddr, addrlen);

      if (iRet)

      {

           perror("Connect failed!");

           return;

      }

      char szBuf[1024];

      char szMsg[1024];

      while(1)

      {

           memset(szBuf, 0, sizeof(szBuf));

           iRet = read(fd, szBuf, sizeof(szBuf));//receive message

           if (iRet < 0)

           {

                 perror("Read failed!");

                 break;

           }

           printf("Recv: %s ", szBuf);

           fprintf(stderr, "Send:");

           memset(szMsg, 0, sizeof(szMsg));

           read(STDIN_FILENO, szMsg, sizeof(szMsg));//get message

          

           write(fd, szMsg, strlen(szMsg)); //send message

      }

      close(fd);

}

int main(int argc, char** argv)

{

      if (argc!=2

         || (strcmp(argv[1], "s") && strcmp(argv[1], "c"))

           )

      {

           printf("Usage: %s [ c | s ] ", argv[0]);

           printf(" c : For start tcp client ");

           printf(" s : For start tcp server ");

           return 0;

      }

     

      if (argv[1][0] == 's')

      {

           Tcp_server();

      }

      else if (argv[1][0] == 'c')

      {

           Tcp_client();

      }

      return 0;

}

TCP搜索端口:

搜素端口与ping的区别:

ping只能确认某IP地址是否存在,当知道IP地址,不知道端口号也无法进行通信。

原文地址:https://www.cnblogs.com/gd-luojialin/p/9216036.html