UC编程之网络通信(TCP/UDP)

时间:2023-03-09 16:05:17
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 }