目录
四、IP地址转换函数(字符串ip-整数,主机、网络字节序的转换)
一、TCP/UDP的区别
UDP:用户数据协议,面向无连接,可以单播、多播、广播,面向数据报,不可靠;
TCP:传输控制协议,面向连接的,可靠的,基于字节流,仅支持单播传输。
区别 | UDP | TCP |
是否创建连接 | 无连接 | 面向连接 |
是否可靠 | 不可靠 | 可靠的 |
连接对象的个数 | 一对一、一对多、多对一、多对多 | 仅支持一对一 |
传输方式 | 面向数据报 | 面向字节流 |
首部开销 | 8个字节 | 最少20个字节 |
适用场景 | 实时应用(视频会议、直播) | 可靠性高的场合(文件传输) |
二、TCP通信流程:
服务器端:被动连接的角色
1、创建一个用于监听的套接字
-监听:监听有客户端的连接
-套接字:其实就是一个文件描述符
2、将监听的文件描述符和与本地的IP和端口号绑定(IP和端口号:服务器的地址信息)
-客户端连接服务器的时候使用的就是这个IP和端口
3、设置监听,监听的fd开始工作
4、阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个和客户端通信的套接字(fd)
5、通信
-接收数据
-发送数据
6、通信结束,断开连接。
客户端的通信流程
1、创建一个用于通信的套接字(fd);
2、连接服务器,需要指定连接的服务器的IP和端口;
3、连接成功,客户端可以直接和服务器通信;
-接收数据
-发送数据
4、通信结束,断开连接。
三、socket地址
sockaddr_in 的结构体:
struct sockaddr_in
{
sa_family_t sin_family; //地址族(Address Family)一般选择AF_INET
uint16_t sin_port; //16位TCP/UDP端口
struct in_addr sin_addr; //32位IP地址
char sin_zero[8]; //不使用
};
struct int_addr
{
int_addr_t s_addr;//int 类型 占4个字节
};
四、IP地址转换函数(字符串ip-整数,主机、网络字节序的转换)
通常,人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPv4地址,以及用十六进制字符串表示IPv6地址。但是编程中我们需要先把他们转化为整数(二进制数)方能使用。而记录日志的时候则相反,我们要把整数表示的IP地址转化为可读的字符串。下面的3个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换。
#include <arpa/inet.h>
int inet_pton(int af,const char *src,void *dst);
// p:点分十进制IP字符串,n:network,网络字节序的整数
-af:地址族 AF_INET AF_INET6
-src:需要转换的点分十进制的IP字节串
-dst:转换后的结果保存在这个里面
char *inet_ntop(int af,const void *src,char *dst,socklen_t size);
-af:地址族,AF_INET AF_INET6
-src:网络字节序整数存放的地方
-dst:要转换的ip地址字符串保存的地方
-size:第三个参数的大小(数组的大小)
-返回值:返回转换后的数据的地址(字符串),和dst是一样的
用代码举个例子
1 #include <stdio.h>
2 #include <arpa/inet.h>
3 int main()
4 {
5 //1、创建一个ip字符串,点分十进制IP字符串
6 char buf[]="192.168.17.110";
7 unsigned int num=0;
8 inet_pton(AF_INET,buf,&num);//将点分十进制的IP字符串转换成网络字节序
9
10 unsigned char *p=(unsigned char *)#
11 printf("%d %d %d %d\n",*p,*(p+1),*(p+2),*(p+3));
12
13 //将网络字节序的IP整数转换成点分十进制字符串
14 char ip[16]="";
15 const char *str=inet_ntop(AF_INET,&num,ip,16);
16 printf("str:%s\n",str);
17 printf("ip:%s\n",ip);
18 return 0;
19
20 }
输出结果为:
由结果可知,按照这样的使用规则是正确的。
五、服务器端和客户端相关的函数
(1)服务器端
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <arpa/inet.h>
6 int main()
7 {
8 //1、创建socket(用于监听的套接字)
9 int lfd=socket(AF_INET,SOCK_STREAM,0);
10 if(lfd==-1)
11 {
12 perror("socket");
13 exit(-1);
14 }
15 //2、绑定
16 struct sockaddr_in saddr;
17 saddr.sin_family = AF_INET;
18 //inet_pton(AF_INET,"192.168.17.134", saddr.sin_addr.s_addr);
19 saddr.sin_addr.s_addr=0;//0.0.0.0或者INADDR_ANY
20 saddr.sin_port=htons(9999);
21 int ret=bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));
22 if(ret==-1)
23 {
24 perror("bind");
25 exit(-1);
26 }
27 //3、监听
28 ret=listen(lfd,8);//同时能接收8个连接
29 if(ret==-1)
30 {
31 perror("listen");
32 }
33 //4、接收客户端连接
34 struct sockaddr_in clientaddr;
35 socklen_t len=sizeof(clientaddr);
36 int num=accept(lfd,(struct sockaddr *)&clientaddr,&len);
37 if(num==-1)
38 {
39 perror("accept");
40 exit(-1);
41 }
42 //输出客户端信息
43 char clientIP[16];
44 inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,clientIP,sizeof(clientIP));
45 unsigned short clientPort = ntohs(clientaddr.sin_port);
46 printf("client ip is %s,port is %d\n",clientIP,clientPort);
47 //5、获取客户端的数据
48 //给客户端发送数据
49 char recvBuf[1024]={0};
while(1)
{
50 int fd=read(num,recvBuf,sizeof(recvBuf));
51 if(fd==-1)
52 {
53 perror("read");
54 exit(-1);
55 }
56 else if(fd>0)
57 {
58 printf("recv client data :%s\n",recvBuf);
59 }
60 else if(len==0)
61 {
62 //表示客户端断开连接
63 printf("client close...");
64 }
65 char *data="hello , i am server";
66 write(num,data,strlen(data));
}
67 close(lfd);
68
69 return 0;
70 }
(2)客户端
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <arpa/inet.h>
6 int main()
7 {
8 //1、创建socket
9 int lfd=socket(AF_INET,SOCK_STREAM,0);
10 if(lfd==-1)
11 {
12 perror("socket");
13 exit(-1);
14 }
15 //2、连接服务器
16 struct sockaddr_in serveraddr;
17 serveraddr.sin_family=AF_INET;
18 inet_pton(AF_INET,"192.168.17.134",&serveraddr.sin_addr.s_addr);
19 serveraddr.sin_port=htons(9999);
20 int ret=connect(lfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
21 if(ret==-1)
22 {
23 perror("connect");
24 exit(-1);
25 }
26 //3、通信
27 //给客户端发送数据
28 char recvBuf[1024]={0};
29 while(1)
30 {
31 char *data="hello , i am client";
32 write(lfd,data,strlen(data)+1);
33 sleep(1);
34 //接收服务器发来的数据
35 int len=read(lfd,recvBuf,sizeof(recvBuf));
36 if(len==-1)
37 {
38 perror("read");
39 exit(-1);
40 }
41 else if(len>0)
42 {
43 printf("recv server data:%s\n",recvBuf);
44 }
45 else if(len==0)
46 {
47 //与服务器断开
48 printf("server closed ...\n");
49 }
50 }
51 //关闭文件描述符
52 close(lfd);
53 // close(len);
54
55
56
57 return 0;
58 }
然后开始运行,运行时一定要先运行服务器端,再运行客户端,否则会报错。
服务器端运行结果为:
客户端运行结果为:
我在写服务器端的时候遇到了一个小插曲,就是我在写read()函数和read()函数时,把第一个位置写成了socket的返回值,发现客户端一直不能接收来自服务端的消息,后来我把这个地方改成 accept()函数的返回值后就一切正常了。
真的受教了!