通常使用“点分十进制”字符串表示IP地址,用3个“.”划分成四个区。每个区的表示范围为0~255
端口号具有2个字节16个比特位的整数
IP是将数据从A主机交付给B主机
端口号用来标识一个进程,一个端口号只能被一个进程占用
IP地址+端口号=套接字,可以进行数据层面的来往,本质上就是进程间的通信
端口号和进程ID的区别:
每一个进程都需要有PID,但不是所有进程都有端口号,除非这个进程投身于网络服务
一个进程可以绑定多个端口号,但是一个端口号不能绑定多个进程
简单对比TCP与UDP:
认识TCP(传输控制协议)协议保证可靠性
超时重传、面向连接、拥塞控制、按需到达、去重
传输层协议
有连接
可靠
面向字节流
认识UDP(用户数据报协议)协议:不保证可靠性
传输层协议
无连接
不可靠
面向数据报
网络字节序列-------------->大端序列,由底层自动帮我们完成
小端:数据的低位放在低地址处
大端:数据的高位放在低地址处
网络数据流的地址规定:先发出的数据是低地址,后发出去的数据是高地址
调用库函数做网络字节序列和主机字节序的转换:
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntonhl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
h表示host,n表示network
编写UDP
创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain,int type,int protocol);
domain:套接字类型
AF_UNIX :域间套接字,用来进行本地通信
AF_INET :使用Ipv4
AF_INET6:使用ipv6
type:表示服务类型
SOCK_DGRAM:无连接不可靠指定大小长度的服务
int protocol:协议类型,因为以上的设定明确表示使用的udp,所以这里不用再设定,直接写0
返回值:成功返回文件描述符(以某种方式打开网卡),失败返回-1
网卡是一个文件,这样就可以被进程操作,进程将操作的文件放入自身的文件描述符表中,通过文件描述符来访问文件描述符表中的不同文件,往文件描述符上读写就是往网卡中读写
所谓文件描述符本质上是PCB中文件描述符表中的下标,以便找到这个文件进行操作
绑定端口号
int bind(int socket,const struct sockaddr *address,socklen_t address_len);
socket:文件描述符
address:绑定的端口号
成功返回0,失败返回-1
开始监听socket
int listen(int socket,int backlog);
接收请求
int accept(int socket,struct sockaddr*address,socklen_t*address_len);
建立连接
int connet(int sockfd,const struct sockadd*addr,socklen_t addrlen);
sockaddr这一类套接字接口可以接收任意类型,类似于void*,但出现在void*之前,可以根据地址类型来具体划分接口类型
我们可以通过netstat -nlup 查看udp服务器
具体实现代码:
服务器端:
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<sys/socket.h>
4 #include<netinet/in.h>
5 #include<arpa/inet.h>
6
7 int main(int argc,char* argv[])
8 {
9 if(argc!=3)
10 {
11 printf("Usage:%s [ip] [port]",argv[0]);
12 return 1;
13 }
14 int sock=socket(AF_INET,SOCK_DGRAM,0);
15 if(sock==-1)
16 {
17 perror("socket");
18 return -1;
19 }
20 struct sockaddr_in local;
21 local.sin_family=AF_INET;//表示底层使用的协议是ipv4
22 local.sin_port=htons(atoi(argv[2]));//使用的端口号,网络中传播的端口号为整形,
23 //这里需要用atoi进行转化,此外要把客户端传递出去还需要将端口号转化为网络字节序列
24 local.sin_addr.s_addr=inet_addr(argv[1]);//使用的ip地址,此时的ip是类似与”192.168.43.243”这样的点分十进制,需要转化成四字节
25 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//绑定本地的端口号local
26 {
27 perror("bind");
28 return -1;
29 }
30 while(1)//不断的对外提供服务
31 {
32 struct sockaddr_in client;
33 socklen_t len=sizeof(client);
34 char buf[1024];
35 ssize_t s=recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);//用sockaddr_in输出型参数来获取客户端的套接字
36 if(s>0)
37 {
38 buf[s]=0;
39
printf([%s:%d]:%s",inet_ntoa(client.sin_addr),ntohs(client.sin_port),buf);//将从客户端获取的网络字节序列转换程本地,将网络的四字节转化程本地
的点分十进制
40 sendto(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,sizeof(client));//接收数据
41 }
42 }
43 return 0;
44 }
客户端:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<sys/socket.h>
5 #include<netinet/in.h>
6 #include<arpa/inet.h>
7 #include<stdlib.h>
8 #include<string.h>
9 //三个命令行参数 可执行文件 服务器ip 服务器端口号
10 int main(int argc,char*argv[])
11 {
12 if(argc!=3)
13 {
14 printf("Usage:%s [ip] [port]",argv[0]);
15 return 1;
16 }
17 int sock=socket(AF_INET,SOCK_DGRAM,0);
18 if(sock==-1)
19 {
20 perror("socket");
21 return -1;
22 }
23 //客户端可以绑定,但是不需要绑定,服务器段必须绑定,因为i服务器必须是众所周知切不可变的
24 while(1)
25 {
26 struct sockaddr_in server;//设置发送方
27 server.sin_family=AF_INET;//设置协议家族
28 server.sin_port=htons(atoi(argv[2]));
29 server.sin_addr.s_addr=inet_addr(argv[1]);//将点分十进制的ip地址转程四字节风格的网络ip地址
30 char buf[1024];
31 printf("Please Enter:");
32 ssize_t s=read(0,buf,sizeof(buf)-1);
33 if(s>0)
34 {
35 buf[s]=0;//为字符串的末尾增加\0
36 sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
37 recvfrom(sock,buf,sizeof(buf)-1,0,NULL,NULL);//设为NULL表示不关心服务器
38 printf("server echo: %s\n",buf);
39 }
40
41 }
42 return 0;
43 }