一、基本概述
1、Socket套接字是系统提供用于网络应用开发的一系列Api接口 , 各个平台语言都有对Socket套接字的支持(兼容性跨平台能力强)。
2、Linux系统下 everything its file ,所以将网络设备抽象成了文件,可以通过文件处理的方式操作访问socket进行网络通信。linux下将socket包裹成文件描述符 sockfd,它跟传统的文件描述符fd不同。传统的文件描述符fd指向磁盘io ,向磁盘中读写数据。sockfd指向网络io 向网络中读写,访问网络设备。只是借用文件描述符特性,与磁盘没有任何关系。
二、基础信息及函数
1、网络信息结构体 (就是socket里面的东西)
1)可以通过自定义网络信息替换socket中的地址信息
2)双方进行网络通信,可以通过将对方的网络信息传出
struct sockaddr_in addr; //网络信息结构体(存储的都是大端序,网络字节序)
addr.sin_family = AF_INET|AF_INET6; //指定ip协议版本
addr.sin_addr.s_addr = 大端序IP地址(存储ip地址)
addr.sin_port = 大端序端口(存储端口)
2、大小端转换函数
大端序/网络字节序:低地址存储高字节,高地址存储低字节。
小端序/主机字节序:低地址存储低字节,高地址存储高字节。
#include <arpa/inet.h>
h表示host 主机 = 小端
n表示net 网络 = 大端
l = ip(long类型32位,IP就是32位)
s = port(short类型16位,端口就是16位)
大端序ip = htonl(小端序ip)
大端序端口 = htons(小端序port)
小端序ip = ntohl(大端序ip)
小端序port = ntohs(大端序端口)
但在操作系统中IP的表现形式就是一个32位数据,我们平常里看到的ip例如192.168.100.221 这种表现形式点分十进制。是为了我们方便观察,但是IP真正的存储就是一个32位的数据 ,我们上面的需要的传入的ip都是32位数据。但我们只知道小端字符串形式例如 192.168.11.164 ,如何转换成32位数据,然后再转换成大端?
下面这两个函数不用关心字符串是如何变成32位小端再转换成大端的 可以直接将字符串(大端)转成大端(字符串)。
1)字符串ip转大端序ip
inet_pton(AF_INET,"192.168.11.145",void * ptr);
//ptr:转后的大端序ip存到哪里 一般都会存到网络信息结构体里面大端序ip的地方
2)大端序ip转字符串ip
inet_ntop(AF_INET,void * ptr, char * iparray , size_t ipsize )
//ptr:要转的大端序ip在哪,iparray:字符串ip传出到哪里,ipsize:传出到的数组的大小
这两个函数返回值都是转换后的对应的大端ip或者字符串ip。
3、SOCKET() 创建socketfd
#include <sys/socket.h>
int sockfd = socket(AF_INET , SOCK_STREAM , 0);
argv1 = 指定IP协议版本 , ipv4 or ipv6 AF_INET(ipv4) AF_INET6(ipv6)。
argv2 = 指定传输层协议, SOCK_STREAM(流式协议) SOCK_DGRAM(报文)。
argv3 = protocal = 0 ,表示默认。写STREAM默认协议为TCP , DGRAM默认协议为UDP。
注意:
1)第二个参数只能确定使用的协议的形式(流式或者报文,tcp和udp只是他们其中的一种,并非流式(报文)协议就是指tcp(udp))。
2)第三个参数具体确定了使用哪种中的哪个协议。
返回值:成功返回Sockfd , 失败返回-1并且ERRNO被设置
4、绑定 BIND 设置socket网络信息
利用SOCKET()函数创建出来的 sockfd里面有默认的ip(本机任意ip)和端口(随机端口)。我们如果需要设置满足需求的ip和端口,定制sockfd中的网络信息,就需要使用bind进行设置。
用自定义网络信息替换socket中的默认网络信息
struct sockaddr_in; //先定义一个网络信息结构体
int reval = bind(int sockfd , struct sockaddr * addr , sizeof(addr));
注意:要传sockaddr类型,而不是新的sockaddr_in addr类型,我们定义新的,传参强转。(历史遗留问题,需要我们向前兼容)
返回值,成功返回0,失败返回-1,并且ERRNO被设置
经典的C/S架构中, 通常Server需要绑定设置 , Client是否需要绑定看需求。
5、LISTEN监听网络(连接)事件(客户端一般不用,因为只有服务端需要监听网络事件,udp不用,tcp用)
listen(int sockfd , 128);//监听的socket与监听序列号
返回值:成功返回0 失败返回-1并且设置ERRNO
6、ACCEPT 服务端等待建立连接(阻塞函数)
int clientfd = accept(int serverfd , struct sockaddr * clientaddr , socklen_t* size);
argv1 = 阻塞在特定sockfd等待连接
argv2 = 连接成功传出对方的网络信息结构体
argv3 = 传入预存储网络信息结构体大小(我们传入能接受多大的),传出实际网络信息结构体大小(内核修改成实际的)
返回值:成功返回客户端的sockfd , 失败返回-1,并且设置ERRNO
7、CONNECT 客户端主动请求建立连接
connect(int clientfd , struct sockaddr * serveraddr , sizeof(serveraddr));
argv1 = 请求端的sockfd
argv2 = 对端(服务端)的网络信息结构体(主动跟谁请求)
argv3 = 网络信息结构体大小
返回值:成功返回0 失败返回-1,并且设置ERRNO
三、基本TCP的C/S模型
四、实例
编写一个支持基本链接功能的服务端模型(TCP),使用nc测试链接,模拟客户端测试。
nc 服务器ip 服务器端口 nc 192.168.27.128 8000
#include<sys/socket.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#define SERVER_IP "192.168.11.162"
#define SERVER_PORT 8000
int main(void)
{
//1、定义初始化网络信息结构体
struct sockaddr_in serveraddr; //服务器自己的
struct sockaddr_in clientaddr; //传出的客户端的
//2、根据需求设置网络信息
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
inet_pton(AF_INET,SERVER_IP,&serveraddr.sin_addr.s_addr); //字符串ip转大端ip
//3、创建sockfd
int serverfd = socket(AF_INET,SOCK_STREAM,0);//创建tcp sockfd
//4、socket与自定义网络信息绑定
bind(serverfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
//5、开启网络链接状态监听
listen(serverfd,128);
//6、服务端阻塞等待链接
int clientfd;
socklen_t addrsize = sizeof(clientaddr);
printf("Server Process Pid %d Accepting... ...\n",getpid());
char arrip[16];
bzero(arrip,16);
if((clientfd = accept(serverfd,(struct sockaddr*)&clientaddr,&addrsize))>0){
//链接成功数据客户端网络信息
printf("Server Process Pid %d Accept Success!\n",getpid());
printf("client IP[%s] PORT[%d].\n",inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,arrip,16),ntohs(clientaddr.sin_port));
}
close(serverfd);
close(clientfd);
return 0;
}