文章目录
- 一、SOCKET
- 1、网络套接字SOCKET
- 2、网络字节序
- 2.1、小端法
- 2.2、大端法
- 2.3、字节序转换
- 3、IP地址转换函数
- 3.1、本地字节序转网络字节序
- 3.1.1、函数原型:
- 3.1.2、返回值
- 3.2、网络字节序转本地字节序
- 3.2.1、函数原型
- 3.2.2、返回值
- 4、sockaddr地址结构(服务器server写法)
- 4.1、struct sockaddr_in addr
- 4.2、struct sockaddr_in结构体
- 5、socket模型创建流程分析
- 6、socket和bind
- 6.1、socket
- 6.1.1、socket函数
- 6.1.2、返回值
- 6.2、bind
- 6.2.1、bind函数
- 7、listen和accept
- 7.1、listen
- 7.1.1函数原型
- 7.1.2、返回值
- 7.2、accept
- 7.2.1、函数原型
- 7.2.2、返回值:
- 8、connect函数(client)
- 8.1.1、函数原型
- 8.1.2、返回值:
- 二、TCP通信案例
- 1、TCP通信模型
- 1.1、需求分析
- 1.2、功能简介
- 2、步骤分析与代码
- 2.1、server
- 2.1.1、步骤分析
- 2.1.2、server.c代码
- 2.2、client
- 2.2.1、步骤分析
- 2.2.2、client.c代码
- 三、三次握手 和四次挥手
- 1、三次握手
- 2、数据通信(三次握手之后)
- 3、四次挥手(关闭连接)
一、SOCKET
1、网络套接字SOCKET
(1)一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
(2)在通信过程中,套接字一定是成对出现的
2、网络字节序
2.1、小端法
计算机本地存储,高位存高地址,低位存低地址
2.2、大端法
网络存储,高位存低地址,低位存高地址
2.3、字节序转换
为了能使计算机和网络之间能够顺利通信,需要进行字节序的转换,就是大小端之间的转换,l表示32位无符号整型,s表示16位短整型,h代表host主机,n代表net网络
(1)htonl—》本地—》网络(IP)
(2)htons—》本地—》网络(port端口号)
(3)ntohl—》网络—》本地(IP)
(4)ntohs—》网络—》本地(port)
3、IP地址转换函数
3.1、本地字节序转网络字节序
3.1.1、函数原型:
Int inet_pton(int af,const char*src,void *dst)//本地字节序(string IP)—》网络字节序
(1)af:AF_INET、AF_INET6
(2)src:传入,IP地址(点分十进制)
(3)dst:传出,转换后的网络字节序的IP地址
3.1.2、返回值
成功:1
异常:0,说明src指向的不是一个有效的IP地址
失败:-1
3.2、网络字节序转本地字节序
3.2.1、函数原型
Const char *inet_ntop(int af,void *src,char *dst,socklen_t,size);//网络字节序—》本地字节序(string IP)
af:AF_INET、AF_INET6
src:网络字节序IP地址
dst:转换后的本地字节序字节序的IP地址(string IP)
size:dst的大小
3.2.2、返回值
成功:dst
失败:NULL
4、sockaddr地址结构(服务器server写法)
4.1、struct sockaddr_in addr
Addr.sin_family=AF_INET/AF_INET6
Addr.sin_port=htons(9527)
Int dst;
Inet_pton(AF_INET,”192.168.22.54”,(void*)&dst)
Addr.sin_addr.s_addr=dst
addr.sin_addr.s_addr=htonl(INADDR_ANY)//[重点]取出系统中有效的任意IP地址,二进制类型
Bind(fd,(sockaddr *)addr,size)
注意,这里sockaddr给bind传参的时候需要强转
4.2、struct sockaddr_in结构体
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address.*/
struct in_addr {
uint32_t s_addr;/* address in network byte order */
};
5、socket模型创建流程分析
一个客户端和一个服务器通信时,除了各自的一个套接字以外,还有一个监听套接字,一共是三个套接字
6、socket和bind
6.1、socket
6.1.1、socket函数
#include<sys/socket.h>
Int socket(int domin,int type,int protocol); //创建一个套接字
(1)Domin:AF_INET、AF_INET6、AF_UNIX(创建本地套接字时使用)
(2)Type:SOCK_STREAM(流式类型)、SOCK_DGRAM(报式类型)
(3)Protocol:0(前面的type有两个,这里的protocol代表的是对前面两种类型的选择,0代表SOCK_STREAM,代表协议是TCP,1代表的是SOCK_DGRAM,代表协议是UDP)
6.1.2、返回值
成功:新套接字所对应的文件描述符
失败:-1,设置errno
6.2、bind
6.2.1、bind函数
#include<arpa/inet.h>
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);//给socket绑定一个地址结构(IP+port)
(1)Sockfd:socket函数返回值
Struct sockaddr_in addr;
Addr.sin_family=AF_INET;
Addr.sin_port=htons(8888);
Addr.sin.s_addr=htonl(INADDR_ANY);//(服务器写法INADDR_ANY)
(2)Addr:(struct sockaddr *)&addr
Addrlen:sizeof(addr) 地址结构的大小
(3)返回值
成功:0
失败:-1 errno
7、listen和accept
7.1、listen
7.1.1函数原型
int listen(int sockfd,int backlog);//设置同时与服务器建立连接的上限数
(1)Sockfd:socket函数返回值
(2)Backlog:上限数值,最大值是128
7.1.2、返回值
成功:0
失败:-1 errno
7.2、accept
7.2.1、函数原型
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);//阻塞等待客户端建立连接,成功的话,返回一个与客户端成功链接的socket文件描述符
(1)Sockfd:socket函数返回值
Addr:传出参数,成功与服务器建立连接的那个客户端的地址结构(IP+port)
(2)Socklen_t client_addr_len=sizeof(addr)
Addrlen:传入传出。&client_addr_len
入:addr的大小;出:客户端addr实际大小
7.2.2、返回值:
成功:能与服务器进行数据通信的socket对应的文件描述符
失败:-1,errno
8、connect函数(client)
8.1.1、函数原型
Int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);//使用现有的socket与服务器建立连接
(1)Sockfd:socket函数返回值
Struct sockaddr_in server_addr;//服务器地址结构
Server_addr.sin_family=AF_INET
Server_addr.sin_port=8087//跟服务器bind时设定的port完全一致
Server_adde.sin_addr.s_addr
Inet_pton(AF_INET,”服务器的Ip地址”,&server_addr.sin_addr.s_addr)
(2)Addr:传入参数,服务器的地址结构
(3)Addrlen:服务器的地址结构的长度
8.1.2、返回值:
成功:0
失败:-1
如果不使用bind绑定客户端地址结构,采用“隐式绑定”
二、TCP通信案例
1、TCP通信模型
1.1、需求分析
创建一个能从客户端输入10次HELLO,从服务端转换成10个hello的通信器。
1.2、功能简介
该通信器可以从客户端发送10条HELLO给服务端,服务端接收之后可以在屏幕上输出10个hello。
2、步骤分析与代码
2.1、server
2.1.1、步骤分析
(1)socket();//创建socket
(2)bind();//绑定服务器地址结构
(3)listen();//设置监听上限
(4)connect();//阻塞监听客户端连接
(5)read(fd);读socket获取客户端数据
(6)toupper();//数据处理
(7)write(fd);
(8)read(fd);
(9)close();
2.1.2、server.c代码
#include<stdio.h> #include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<sys/socket.h>
#include<ctype.h>
#include<arpa/inet.h>
#define SERVER_PORT 8087
void sys_err(char*str)
{
perror(str);
exit(1);
}
int main(int argc,char*argv[])
{
int lfd=0,cfd=0;
int ret,i;
char buf[BUFSIZ],client_IP[1024];
int conter=10;
struct sockaddr_in server_addr,client_addr;
socklen_t client_addr_len;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1)
{
sys_err("socket error");
}
bind(lfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
listen(lfd,128);
client_addr_len=sizeof(client_addr);
cfd=accept(lfd,(struct sockaddr *)&client_addr,&client_addr_len);
if(cfd==-1)
{
sys_err("accept error");
}
printf("client ip:%s port:%d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(client_addr.sin_port));
while(--conter)
{
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
for(i=0;i<ret;i++)
{
buf[i]=toupper(buf[i]);
}
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 0;
}
2.2、client
2.2.1、步骤分析
(1)socket();创建socket
(2)connect();与服务器建立连接
(3)write();
(4)read();
(5)显示读取结果
(6)close();
2.2.2、client.c代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#define SERVER_PORT 8087
void sys_err(char*str)
{
perror(str);
exit(1);
}
int main(int argc,char*argv[])
{
int cfd;
int conter=10;
char buf[BUFSIZ];
struct sockaddr_in server_addr;
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(SERVER_PORT);
inet_pton(AF_INET,"127.0.0.1",&server_addr.sin_addr.s_addr);
cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1)
{
sys_err("socket error");
}
int ret=connect(cfd,(struct sockaddr *)&server_addr,sizeof(server_addr));
if(ret!=0)
{
sys_err("connect error");
}
while(--conter)
{
write(cfd,"hello\n",6);
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
sleep(1);
}
close(cfd);
return 0;
}
三、三次握手 和四次挥手
1、三次握手
1、客户端发送一个STN标志位。500(0)表示的是发送一个包号为500,数据大小为0的数据包,从而告知服务器,我要与你建立连
2、此时服务器会回应一个ACK,表示我接收到了你的请求,501号包以前的数据我都收到了。服务器也会发送一个SYN标志位,告诉客户端,我要和你建立连接
3、客户端收到服务器的请求以后,也会回应一个ACK,表示请求收到了。三次连接的过程发生在内核。在用户层面的体现就是
accept (内核)和connect(用户)函数成功执行并返回了
2、数据通信(三次握手之后)
客户端发送数据时,又发送了一个ACK701,是为了确保服务器连接成功,因为如果三次握手时发送的ACK回应服务器没收到,就不能成功发送数据。
3、四次挥手(关闭连接)
四次挥手的原因是:半关闭
三次握手之后成功建立连接,服务器可以向客户端发送数,客户端也可以向服务器发送数据。这里客户户端关闭连接之后,服务器就只能发送数据,不能读取读取数据了。
为什么客户端半关闭之后,服务器可以向客户端发送数据?因为套接字里面有两个缓冲区,一个读缓冲区和一个写缓冲区,半关闭就相当于把写缓冲区关掉了,客户端无法向服务器发送数据,但是可以读服务器发送过来的数据。