一.套接字(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;
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()创建套接字,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 }
结果: