UC编程之网络通信(TCP/UDP)
网络常识
OSI 7层模型(人机交互)
物理层、数据链路层、网络层、传输层、会话层、表现层、应用层
常见协议:
tcp/udp/ip/ftp/http...
IP地址--就是计算机在网络中的地址,是一个32位的整数(IPV4),目前也有IPV6
IP地址在计算机中,以一个整数格式保存。因此IP地址在底层的描写方式:8位16进制。点分10进制是人类描述IP地址的主要方式。每个字节计算一个10进制的整数,中间用 “.”隔开。
192.168.0.20(点分十进制0-255)
==0xC0 A8 00 14(8位16进制)
IP地址分为A B C D 4类。
子网掩码--判断计算机是不是在一个局域网上
166.111.160.1与166.111.161.45
子网掩码:255.255.254.0
166.111.160.1
255.255.254.0 (位与)
--------------
166.111.160.0
166.111.161.45
255.255.254.0 (位与)
--------------
166.111.160.0
结论:166.111.160.1与166.111.161.45在同一个局域网
IP地址只能定位计算机,但没有访问权限。
端口会开放访问的权限。端口可以用来定位计算机中的某个进程。
网络编程必须提供IP地址和端口号。
端口号是unsigned short,范围:0-65535
0-1023 固有端口(不推荐使用)计算机预留
1024-48XX 程序员使用的端口 安装某些程序也会占用,但很少
48XX - 65535 不建议用,不稳定
MAC地址,物理地址,是网卡的物理地址。IP地址和MAC地址绑定在一起。
字节次序--计算机在存储整数时,有从高到低 和 从低到高之分,叫字节次序。字节次序在计算机中不确定,在网络传输的过程中是固定的。
因此,对于端口,需要本地格式和网络格式之间的转换。
域名:俗称网址,就是IP地址的助记,通过域名解析服务器 网址解析成IP地址完成访问。
网络编程--Windows/Unix 都支持
socket编程
socket--插座、套接字(IP + 端口)
关于socket编程的一些函数和常识
socket编程分为:一对一 和 一对多
一对一:
socket通信包括本地通信(ipc)和网络通信
一对一通信模型:
服务器端编程步骤:
1 创建一个socket,使用socket()
int socket(int domain,int type,int protocol)
domain--域,用来选择协议
PF_UNIX PF_LOCAL PF_FILE 本地通信
PF_INET 网络通信
PF_INET6 网络通信(IPV6)
注:PF也可以换成AF
type--用来选择通信类型
SOCK_STREAM --数据流,针对TCP协议
SOCK_DGRAM --数据报,针对UDP协议
protocol--本来应该用来指定协议,但没用了,因为协议已经被前2个参数指定,给 0即可
返回socket描述符,失败返回-1
2 准备通信地址
关于通信地址有3个结构体:
struct sockaddr{
int sa_family;//协议
char sa_data[];//地址
}
这个结构sockaddr不被真正使用,只是用来做相关函数的参数(不存数据)。
本地通信使用结构体:
#include<sys/un.h>
struct sockaddr_un{
int sun_family;//协议
char sun_path[];//socket文件的路径
}
网络通信使用结构体:
#include<netinet/in.h>
struct sockaddr_in{
int sin_family;//协议
short port;//端口号
struct in_addr sin_addr;//IP地址
}
3 绑定函数bind()
bind(int sockfd,sockaddr,length)
4 通信(read/write)
5 关闭 close(sockfd)
客户端编程步骤:
与服务器端基本一样,除了第三步,第三步使用connect(),参数与bind一样
注:服务器和客户端数据交互时,读写必须必须保持一致性(一边读,另一方写)
查看本机IP地址的命令:
Windows --ipconfig
Unix -- ifconfig
whereis 可以查看命令所在的目录
ping IP地址 可以检测网络是否畅通
本地通信实例:
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<sys/un.h>
7
8 int main(){
9 int sockfd = socket(PF_UNIX,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_un addr;
12 addr.sun_family = PF_UNIX;//与socket第一个参数保持一致
13 strcpy(addr.sun_path,"a.sock");
14 int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
15 if(res == -1) perror("bind"),exit(-1);
16 printf("bind ok!\n");
17 char buf[100]={};
18 res = read(sockfd,buf,sizeof(buf));
19 printf("读了%d字节k,内容%s\n",res,buf);
20 close(sockfd);
21 return 0;
22 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<sys/un.h>
7
8 int main(){
9 int sockfd = socket(PF_UNIX,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_un addr;
12 addr.sun_family = PF_UNIX;
13 strcpy(addr.sun_path,"a.sock");
14 int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
15 if(res == -1) perror("connect"),exit(-1);
16 printf("connect ok!\n");
17 write(sockfd,"hello",5);
18 close(sockfd);
19 return 0;
20 }
网络通信:
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7
8 int main(){
9 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port =htons(2222);//端口
14 addr.sin_addr.s_addr =inet_addr("192.168.13.73");//服务器ip地址
//inet_addr将点分十进制转换成整数ip
15 int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
16 if(res == -1) perror("bind"),exit(-1);
17 char buf[100]={};
18 res =read(sockfd,buf,100);
19 printf("读到了res=%d字节,内容是%s\n",res,buf);
20 return 0;
21 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7
8 int main(){
9 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port =htons(2222);
14 addr.sin_addr.s_addr =inet_addr("192.168.13.73");/服务器ip地址
//inet_addr将点分十进制转换成整数ip
//服务器端和客户端的ip都是服务器ip地址
15 int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));
16 if(res == -1) perror("bind"),exit(-1);
17 write(sockfd,"hello",5);
18 return 0;
19 }
TCP 一对多编程步骤:
服务器端:
1 调用socke()创建socket,type必须是SOCK_STREAM(保证使用TCP模式)
2 准备通信地址sockaddr_in
3 bind()绑定
4 监听 listen(),为accept()做准备
5 等待客户端的连接accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
参数sockfd就是第一步的socket
参数addr就是链接上来的客户端的通信地址
参数len是一个传入传出参数,传入通信地址的大小,传出客户端通信地址 的大小
(要准备一个sockaddr_in 结构来存放客户端通信地址)
返回新的socket描述符,用来和客户端进行通信
6 读写数据 read/write
7 关闭socket
注:第一步的socket主要用于等待连接,不参与信息交互。第五步的socket主要用于和客户端之间的通信
客户端:
1 调用socke()创建socket,type必须是SOCK_STREAM(保证使用TCP模式)
2 准备通信地址sockaddr_in
3 connect()绑定
4 通信(read/write)
5 关闭 close(sockfd)
TCP---一对多实例:
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/socket.h>
5 #include<netinet/in.h>
6 #include<arpa/inet.h>
7 #include<signal.h>
8 int sockfd;
9 void fa(){
10 close(sockfd);
11 printf("服务器关闭\n");
12 exit(0);
13 }
14 int main(){
15 signal(SIGINT,fa);//ctrl+c 信号关闭服务器
16 sockfd = socket(PF_INET,SOCK_STREAM,0);//创建
17 if(sockfd == -1) perror("socket"),exit(-1);
18 struct sockaddr_in addr;
19 addr.sin_family = PF_INET;
20 addr.sin_port = htons(2222);//端口
21 addr.sin_addr.s_addr = inet_addr("192.168.13.85");//服务器ip
22 //解决重启时地址被占用问题
23 int reuseaddr = 1;
24 setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,
&reuseaddr,sizeof(reuseaddr) );
25 int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));
//绑定,激活端口
26 if(res == -1) perror("bind"),exit(-1);
27 printf("bind ok\n");
28 listen(sockfd,100);//监听
29 while(1){
30 struct sockaddr_in from;//存放连接的客户端信息
31 socklen_t len = sizeof(from);
32 int fd = accept(sockfd,(struct sockaddr*)&from,&len);
//等待客户端连接
33 char* fromip = inet_ntoa(from.sin_addr);
34 printf("客户端%s连接成功\n",fromip);
35 pid_t pid = fork();//创建子进程(目前还为学习线程)
36 if(pid == 0){
37 while(1){
38 char buf[100] = {};
39 read(fd,buf,100);
40 printf("buf=%s\n",buf);
41 if(strcmp(buf,"byp")==0) break;
42 write(fd,buf,strlen(buf));
43 }
44 close(fd);
45 exit(0);
46 }
47 close(fd);
48 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<string.h>
4 #include<sys/socket.h>
5 #include<netinet/in.h>
6 #include<arpa/inet.h>
7
8 int main(){
9 int sockfd = socket(PF_INET,SOCK_STREAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port = htons(2222);//端口
14 addr.sin_addr.s_addr = inet_addr("192.168.13.85");//服务器ip
15 int res = connect(sockfd,(struct sockaddr*)&addr,sizeof(addr));//连接
16 if(res == -1) perror("bind"),exit(-1);
17 printf("connect ok\n");
18 while(1){
19 char word[100] ={};
20 printf("请输入要说的话\n");
21 scanf("%s",word);
22 write(sockfd,word,strlen(word));
23 if(strcmp(word,"bye")==0) break;
24 char buf[100] = {};
25 read(sockfd,buf,100);
26 printf("buf=%s\n",buf);
27 }
28 close(sockfd);
29 return 0;
30 }
基于TCP协议的服务器和客户端直接的通信除了用read()/write()还可以用
recv()/send()
UDP --用户数据报协议
关于TCP和UDP的区别:
TCP -- 有连接协议,在通信的全程保持连接
TCP优点:重发一切错误数据,保证数据的正确和完整,缺点:服务器端压力
非常大,资源占用率比较高。
UDP -- 无连接协议,在发送数据的时候连接一下,不保持任何的连接
UDP优点:效率高,资源占用少。缺点:不保证数据的完整和正确。
UDP网络编程的函数 --发送数据和接受数据
sendto() 和 recvfrom()
几点注意:
1 第二步准备通信地址,都是服务器端的通信地址
2 客户端的通信地址,TCP用accept()函数拿,UDP用recvfrom拿(发送端口)。
客户端的端口是自动分配的。
3 TCP的信息交互函数:read/write/send/recv
UDP的信息交互函数:read/write/send/recv,但上面的函数不能取得发送方通信地址,因此更多时候,使用sendto/recvfrom
4 网络信息接收函数 一般会阻塞代码
5 服务器端都必须使用bind(),bind()函数的作用就是服务器端开发一个端口(把端口和进程绑定起来)。而客户端自动完成的。
UDP实例:
服务器端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/socket.h>
4 #include<netinet/in.h>
5 #include<arpa/inet.h>
6 #include<string.h>
7
8 int main(){
9 int sockfd =socket(PF_INET,SOCK_DGRAM,0);//创建
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port = htons(2222);//端口
14 addr.sin_addr.s_addr = INADDR_ANY;//本机IP
15 int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));//开放端口
16 if(res == -1) perror("bind"),exit(-1);
17 printf("bind ok\n");
18 char buf[100] ={};
19 struct sockaddr_in from;//用于保存客户端信息,以便回发信息
20 socklen_t len = sizeof(from);
21 recvfrom(sockfd,buf,100,0,(struct sockaddr*)&from,&len);//接收信息
22 //read(sockfd,buf,100);
23 printf("buf=%s\n",buf);
24 sendto(sockfd,"welcome",7,0,(struct sockaddr*)&from,len);//发送信息
25 close(sockfd);
26 return 0;
27 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/socket.h>
4 #include<netinet/in.h>
5 #include<arpa/inet.h>
6 #include<string.h>
7
8 int main(){
9 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port = htons(2222);
14 addr.sin_addr.s_addr = INADDR_ANY;
15 // write(sockfd,"hello",5);没有接收方地址
16 sendto(sockfd,"hello",5,0,(struct sockaddr*)&addr,sizeof(addr));
17 char buf[100] ={};
18 // read(sockfd,buf,100);
19 struct sockaddr_in from;
20 socklen_t len =sizeof(from);
21 recvfrom(sockfd,buf,100,0,(struct sockaddr*)&from,&len);
22 char* fromip = inet_ntoa(from.sin_addr);
23 printf("%s发来%d字节信息\n",fromip,len);
24 printf("%s\n",buf);
25 close(sockfd);
26 return 0;
27 }
时间服务器:
服务端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/socket.h>
4 #include<netinet/in.h>
5 #include<arpa/inet.h>
6 #include<string.h>
7 #include<signal.h>
8 #include<time.h>
9
10 int sockfd;
11 void fa(int signo){
12 close(sockfd);
13 printf("关闭服务器成功!\n");
14 exit(-1);
15 }
16 int main(){
17 signal(SIGINT,fa);
18 int sockfd =socket(PF_INET,SOCK_DGRAM,0);
19 if(sockfd == -1) perror("socket"),exit(-1);
20 struct sockaddr_in addr;
21 addr.sin_family = PF_INET;
22 addr.sin_port = htons(2222);//端口
23 addr.sin_addr.s_addr = INADDR_ANY;//服务器IP
24 int res = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));//开放端口
25 if(res == -1) perror("bind"),exit(-1);
26 printf("bind ok\n");
27 while(1){
28 char buf[100] ={};
29 struct sockaddr_in from;
30 socklen_t len = sizeof(from);
31 recvfrom(sockfd,buf,100,0,(struct sockaddr*)&from,&len);
32 if(strcmp(buf,"hello")==0){
33 char ts[100]={};
34 time_t curtime = time(0);//获得当前时间秒数
35 struct tm* cur = localtime(&curtime);//将时间秒数转换成tm结构
36 sprintf(ts,"%4d-%02d-%02d %02d:%02d:%02d",
cur->tm_year+1900,cur->tm_mon+1,cur->tm_mday,
cur->tm_hour,cur->tm_min,cur->tm_sec) ;
38 sendto(sockfd,ts,strlen(ts),0,(struct sockaddr*)&from,len);//发送
39 }
40 }
41 return 0;
42 }
客户端:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/socket.h>
4 #include<netinet/in.h>
5 #include<arpa/inet.h>
6 #include<string.h>
7
8 int main(){
9 int sockfd = socket(PF_INET,SOCK_DGRAM,0);
10 if(sockfd == -1) perror("socket"),exit(-1);
11 struct sockaddr_in addr;
12 addr.sin_family = PF_INET;
13 addr.sin_port = htons(2222);
14 addr.sin_addr.s_addr = INADDR_ANY;
15 // write(sockfd,"hello",5);没有接收方地址
16 sendto(sockfd,"hello",5,0,(struct sockaddr*)&addr,sizeof(addr));
17 char buf[100] ={};
18 // read(sockfd,buf,100);
19 struct sockaddr_in from;
20 socklen_t len =sizeof(from);
21 recvfrom(sockfd,buf,100,0,(struct sockaddr*)&from,&len);
22 char* fromip = inet_ntoa(from.sin_addr);
23 printf("%s发来%d字节信息\n",fromip,len);
24 printf("%s\n",buf);
25 close(sockfd);
26 return 0;
27 }