一.套接字(Socket)
在通信过程中,套接字一定是成对出现的(通信双方各持一个)
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现读写),即一个套接字只有一个文件描述符,但有两个缓存区,与管道正好相反。
Linux套接字实现原理:
 
二.预备知识
1.网络字节序和本地字节序
 内存中的多字节数据相对于内存地址有大端和小端之分
小端法:(PC本地存储)高位存高地址,低位存低地址
 大端法:(网络存储)高位存低地址,低位存高地址
网络的数据流采用大端字节序,而本地数据流采用小端字节序.
 因此要通过函数来转换:
 htonl : 本地 -->网络 (IP)
 htons : 本地 --> 网络 (port端口)
 ntohl : 网络 --> 本地 (IP)
 ntohs : 网络 --> 本地 (port)

 此函数转换端口号字节序容易,但是进行IP的大小端转换比较麻烦,因为linux中ip为字符串类型,而函数需要的是整数,需要经过多次转换,所以linux提供了以下封装好的转换函数inet_pton / inet_ntop。
2. ip转换函数
 两个函数,inet_pton 和 inet_ntop
①int inet_pton(int af,const char *src, void *dst)
 本地字节序—>网络字节序
参数:
 af:AF_INET、AF_INET6 ------------------>宏,分别对应IPV4和IPV6
 src:传入,IP地址(点分十进制)
 dst:传出,转换后的 网络字节序的 IP地址
返回值:
 成功:1
 异常: 0,说明src指向的不是一个有效的ip地址。
 失败:-1
②const char *inet_ntop(int af, const void *src, char *dst,socklen t size)
 网络字节序 —>本地字节序
 参数:
 af:AF INET、AF INET6
 src:网络字节序IP地址
 dst:本地字节序(stringIP)
 size:dst 的大小。
返回值:
 成功:dst
 失败:NULL
3.sockaddr数据结构
 头文件#include <arpa/inet.h>
 网络操作函数大部分需要的参数类型为sockaddr类型,这是为了向前兼容。
 但实际使用时,我们先利用sockaddr_in将结构体内容设置好,然后再利用指针转化为sockaddr类型。

  1. sin_family:表示你要使用的地址结构类型,AF_INET是IPV4,AF_INET6是IPV6;
1. sin_family:表示你要使用的地址结构类型,AF_INET是IPV4,AF_INET6是IPV6;
 2. sin_port:网络字节序的端口号,因此要使用htons转换一下;
 3. struct in_addr:一个结构体,里面有一个s_addr,要传入网络字节序的ip地址,因此要使用inet_pton函数;也可以使用第二种方法,宏,如INADDR_ANY(任意可用IP地址)
三.Socket编程函数
虽然说网络通信,socket成对出现,但实际上一次通信,会存在多个Socket。
当服务器端调用socket函数时会产生一个套接字,而accept函数会阻塞监听客户端连接,当有客户端连接的时候 该函数会返回一个新的套接字去和客户端连接,旧的套接字用以监听连接,因此一个socket通信模型中,会有三个套接字存在(客户端,服务端(由accept函数返回)和 监听socket)
通信模型图解:
  客户端:socket()创建套接字,connect()建立连接。
客户端:socket()创建套接字,connect()建立连接。
 服务端:socket()创建套接字,bind()绑定ip和端口,listen()用来设置与服务端连接的客户端数量上线,accept()则用于结束客户端连接,没有请求时则阻塞在该函数上。
1.socket()
头文件: #include <sys/socket.h>
 int socket(int domain, int type, int protocol); 创建一个套接字
 参数:
 domain:ip版本,AF_INET或AF_INET6
 type:数据传输协议 SOCK_STREAM(流式协议,TCP)或SOCK_DGRAM(UDP)
 protocol:默认传0,根据type来选择,SOCK_STREAM的代表协议是TCP,SOCK_DGRAM的是UDP
 返回值:
 成功:新套接字所对应的文件描述符;失败:-1 error
2.bind()
 给socket通过sockaddr结构体绑定地址结构的函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:
 sockfd:socket文件描述符
 addr:传入参数 (struct sockaddr *)&addr
 addrlen:sizeof(addr) 地址结构的大小
返回值:
 成功是0,失败-1 error
3.listen 函数
设置同时与服务器建立连接的上限数(同时进行3次握手的客户端数量);
 int listen(int sockfd, int backlog);
参数:
 sockfd:socket文件描述符
 backlog:上限数值
返回值:
 成功是0,失败-1 error
4.accept函数
阻塞等待客户端建立连接,成功的话 返回一个与客户端成功连接的socket文件描述符。
 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数:
 sockfd:socket的返回值
 addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(ip+端口) 
 addrlen:&clit_addr_len 传入传出参数,入:addr的大小 出:客户端addr的实际大小
 socklen_t clit_addr_len = sizeof(addr); &clit_addr_len
 返回值:
 成功:能与服务器进行数据通信的 socket 对应的文件描述符
 失败:-1 error
5.connect函数
 客户端使用,使用现有的socket与服务器建立连接的函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
 参数:
 sockfd:socket的返回值
 addr:服务器的地址结构
 addrlen:服务器地址结构的长度
 返回值:
 成功:0 失败:-1 error
四.C-S模型通信案例实现
SERVER
  1 #include<iostream>2 #include<unistd.h>3 #include<fcntl.h>4 #include<sys/socket.h>5 #include<arpa/inet.h>6 #include<ctype.h>7 using namespace std;8 int main()9 {10   //1.socket11   int fd,sfd;12   fd = socket(AF_INET,SOCK_STREAM,0);//AF_INET ->IPV4  SOCK_STREAM -> TCP13   //2.bind14   struct sockaddr_in serv_addr,client_addr;15   serv_addr.sin_family = AF_INET ;16   serv_addr.sin_port = htons(9527);17   serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //宏,INADDR_ANY,系统选择可用ip        18   bind(fd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));19   //3.listen20   listen(fd,128);21   //4.accept22   socklen_t clientaddr_len;  23   clientaddr_len = sizeof(client_addr);24   sfd =  accept(fd,(struct sockaddr*)&client_addr,(socklen_t*)&clientaddr_len);25   if(sfd == -1)                                                                                               26   {27      perror("accept error");28      exit(0);29   }30   //取出client_addr,利用inet_ntop和ntohs获取客户端地址结构:ip,端口等31   char client_ip[1024];32   cout<<"ip:"<<inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,client_ip,sizeof(client_ip))<<endl;33   cout<<"port:"<<ntohs(client_addr.sin_port)<<endl;34   //5.连接完成,处理逻辑35   char buf[BUFSIZ];36  while(true)37  {38   int p = read(sfd,buf,sizeof(buf));//接收客户端数据39   write(STDOUT_FILENO,buf,p);40   for(int i = 0;i<p;i++)41     {42      buf[i] = toupper(buf[i]);43     }44   write(sfd,buf,p);  //发给客户端45  }46   return 0;47 }CLIENT
#include<iostream>2 #include<unistd.h>3 #include<sys/socket.h>4 #include<arpa/inet.h>5 using namespace std;6 7 int main()8 {9   int fd;10   //1.socket11   fd = socket(AF_INET,SOCK_STREAM,0);12   //2.connet13   struct sockaddr_in sockaddr;14   sockaddr.sin_family = AF_INET;15   sockaddr.sin_port = htons(9527);16   inet_pton(AF_INET,"127.0.0.1",&sockaddr.sin_addr.s_addr);17 18   connect(fd,(struct sockaddr*)&sockaddr,sizeof(sockaddr));19   //3.通信20   char buf[BUFSIZ]; //409621   while(true)22   {   23     write(fd,"hello\n",sizeof("hello\n"));24     int ret = read(fd,buf,sizeof(buf));25     write(STDOUT_FILENO,buf,ret);26     sleep(1);27   }28   return 0;29 }结果:
 
