基于udp协议实现网络通信:(套接字实现)
客户端 —> 服务端
Socket接口:通过网卡将数据传输出去,操作系统提供了一套网络编程接口
怎么操作网卡?—>(网卡是设备–>linux一切皆文件–>操作网卡就是操作文件,socket也是文件以(d目录文件-普通文件b块设备文件c字符设备文件l软链接文件(符号链接文件)p管道文件s套接字文件(平时看不到)))
对于客户端:步骤
1.创建套接字(操作系统内部涉及到创建文件描述信息,创建套接字的信息创建各自结构和网卡直接建立关联并且返回套接字描述符(本质文件描述符)),建立与网卡的关联
2.对客户端(主动发起请求方告诉别人的地址发送哪里去)就是为套接字绑定地址信息(告诉套接字,操作网卡向外发送数据的时候写源地址源端口写多少(ipdi地址端口放进去))但是对于客户端不推荐手动绑定,会失败(希望操作系统绑定对于套接字绑定源端口和源地址可能会导致失败(已经被占用了),一个端口不能被多个进程使用只能被一个进程使用(会导致数据二义性),但是一个进程可以有多个端口,意味着别人把端口占了自己就没办法发送数据了所以对于客户端程序不推荐绑定地址信息,)
如果不手动绑定在发送数据的时候操作系统发现没有绑定信息,会替我们自动选择合适的地址端口进行绑定(减少了绑定的失败率)
3.发送数据:
客户端不能先接受数据(eg:qq聊天写入了腾讯服务器地址和端口才能将数据发送到同一服务器,腾讯服务器不会记录客户端的ip地址(地址动态分配,上网ip地址不定,每次上网ip地址不定所以没必要记录,)腾讯不知道客户端地址,–>客户端主动发起请求方否则服务端不知道地址信息没办法给它发送数据)
所以先发送数据然后才能接受信息(发送数据已经携带了源端口信息和源地址回复的时候才能回过来)
4.接受数据
5.关闭套接字释放资源(文件描述符占位(inode结点))
对于服务端:
1.创建套接字(建立网卡之间关联)
2.为套接字绑定地址信息(必须自己绑定固定)
3.接受数据
4.发送数据
5.关闭套接字
Udp涉及的关键字:1创建套接字socket() 2.绑定地址信息bind() 3.发送数据(数据发送到哪里去) sedto() 4.接受数据(从哪接受数据接受的是什么数据) recvfrom() 5.关闭套接字 close()
/网卡接收到网络数据之后会先把数据放到内存中做缓冲(不缓冲没处放丢掉了)如何让在
12 //接受的时候接受的是自己的数据(qq接受的是qq数据而不是微信数据),网卡在接收数据的
13 //时候看数据发送到哪个地址端口(相当于创建缓冲区的时候也是根据地址信息创建,每一个
14 //套接字都有一个缓冲区),那么给一个套接字创建一个套接字给套接字绑定了一个地址端口 15 //信息这时候网卡接收到信息后数据之后是哪个地址和端口的会看下操作系统上面到底是
16 //哪个套接字绑定的这个地址端口然后将相应的数据放到这块缓冲区里面就可以了,这时候
17 //通过套接字描述符操作获取数据的时候只需要到缓冲区拿数据就可以了–网卡对内存有 18 //标识,创建套接字起始创建了一个文件描述符信息,并且在操作系统内核创建了一个结构
19 //体叫struct socket,结构体里面有两个队列一个是接收数据队列一个是发送数据队列这
20 //两个队列对应的就是缓冲区(该结构体(套接字)里面有地址信息sip sport,dip dport,)
21 //有这么多的描述信息还有一个缓冲区意味着网卡接收到数据看到该数据地址信息就放到 22 //指定缓冲区里去了—>每个套接字都有缓冲区发送和接收

int socket(int domain, int type, int protocol);
地址域:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication(本地通信) unix(7)
AF_INET IPv4 Internet protocols(iPv4的网络协议) ip(7)
AF_INET6 IPv6 Internet protocols(iPv6的网络协议) ipv6(7)

AF_PACKET Low level packet interface(数据分用把程序层层解析,低级 packet(7)
包接口直接从网卡抓包(意味AF_PACKET接受到的数据包没有
经过数据分用过程的数据包具有以太头网络ip头,tcp/udp头,
应用头–>整体的包数据)

套接字类型
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmis‐sion mechanism may be supported.–流式套接字–提供的是字节流服务—>用于tcp
SOCK_DGRAM Supports datagrams (connectionless, unreliable mes‐ sages of a fixed maximum length).—数据报套接字—提供用户数据报服务–>udp
协议有自己的宏—>vim /usr/include/netinet/in.h里面
可以看到
enum
42 {
43 IPPROTO_IP = 0, /* Dummy protocol for TCP. /
44 #define IPPROTO_IP IPPROTO_IP
45 IPPROTO_ICMP = 1, /
Internet Control Message Protocol. /
46 #define IPPROTO_ICMP IPPROTO_ICMP
47 IPPROTO_IGMP = 2, /
Internet Group Management Protocol. /
48 #define IPPROTO_IGMP IPPROTO_IGMP
49 IPPROTO_IPIP = 4, /
IPIP tunnels (older KA9Q tunnels use 94). /
50 #define IPPROTO_IPIP IPPROTO_IPIP
51 IPPROTO_TCP = 6, /
Transmission Control Protocol. /
52 #define IPPROTO_TCP IPPROTO_TCP
53 IPPROTO_EGP = 8, /
Exterior Gateway Protocol. /
54 #define IPPROTO_EGP IPPROTO_EGP
55 IPPROTO_PUP = 12, /
PUP protocol. /
56 #define IPPROTO_PUP IPPROTO_PUP
57 IPPROTO_UDP = 17, /
User Datagram Protocol. /
58 #define IPPROTO_UDP IPPROTO_UDP
是个枚举对枚举重新定义了宏,就是协议它的宏tcp是6udp是17–>是他们协议的编号也就意味着可以直接给数字用tcp给6用udp给17
Man 2 bind
//2为套接字绑定信息
25 //int bind(int sockfd, struct sockaddr my_addr, socklen_t addrlen);
26 //第一个套接字描述符
27 //第二个地址信息(ip地址端口号)struct socketaddr{sa_family sa_family;
28 //char sa_data[14]};
29 //第三参数地址信息长度
30 //返回值成功0失败-1
struct sockaddr_in//iPv4地址信息结构
239 {
240 _SOCKADDR_COMMON (sin);//域留项目两个字节
241 in_port_t sin_port; /
Port number. /端口号 unint16_t
242 struct in_addr sin_addr; /
Internet address. /地址信息(结构体 )
Typedef uint32_t in_addr_t; struct in_addr{ in_addr_t s_addr;};是一个unint_32的数据
243 //sin_addr 将unit_32数据封装了两层
244 /
Pad to size of `struct sockaddr’. /
245 unsigned char sin_zero[sizeof (struct sockaddr) -
246 __SOCKADDR_COMMON_SIZE -
247 sizeof (in_port_t) -
248 sizeof (struct in_addr)];
249 };//结构体总长度-SOCKADDR_COMMON_SIZE-端口长度in_port_t-地址信息的长度struct in_addr=预留长度—结构体总体长度就是sockaddr
为什么 这么复杂(先用sockaddr然后用sockaddr_In )—>地址信息除了ipv4还有其他的AF_LOCAL,和ipv6, sockaddr_in 6是ipv6的地址信息函数只有一个如果确定类型则只能绑定ipv4的无法绑定ipv6和AF_LOCAL,为了让接口通用 起来绑定各种地址信息拿了个结构体进行了伪装(为了通用性)
Netstat网络状况监听sudu netstat -anptu |grep 9000
anptu:a查看所有的网络状态信息,n:不要使用服务名称替换Ip地址t:指tcp,u:指udp,p:查看进程名字
Udp(协议) 0 (发送队列数据) 0(接受队列) 192.168.96.128:9000(本地地址信息) 0.0.0.0:
(对端地址信息–>udp本身没有对端) 6597/./udp_serv
linux套接字--udp协议实现网络通信
如下图:
linux套接字--udp协议实现网络通信
服务端程序
int main(){
11 //1创建套接字建立网卡之间的关联 12 //2为套接字绑定信息
13 //3接受数据
14 //4发送数据 15 //5关闭套接字
16 //int socket(int domain, int type, int protocol);
17 //domain:地址域(协议族) AF_INET—>ipv4
18 //type套接字类型参数三协议流式套接字默认给0指的是tcp数据报套接字默认给0指的是u dp
19 //参数2:SOCK_STREAM IPPROTO_TCP 6 流式套接字默认协议
20 // SOCK_DGRAM IPPROTO_UDP 17数据报套接字默认udp
21 //返回值成功返回一个文件描述新的socket(为新的套接字返回一个文件描述符)失败-1
22 int socketfd=socket(AF_INET,SOCK_DGRAM,17);
23 if(socketfd<0){
24 perror(“socket erron\n”);
25 return -1;
26 }
//2为套接字绑定信息
28 //int bind(int sockfd, struct sockaddr my_addr, socklen_t addrlen);
29 //第一个套接字描述符
30 //第二个地址信息(ip地址端口号)struct socketaddr{sa_family sa_family;
31 ////地址域什么样的地址信息
32 //char sa_data[14]//真正的地址信息如何排布各个自己的结构体就好};
33 //第三参数地址信息长度
34 //返回值成功0失败-1
35 struct sockaddr_in addr;
36 addr.sin_family=AF_INET;
37 //转换htons
38 //uint16_t htons(uint16_t hostshort);短整型的主机字节序到网络字节序的转换–>
39 //两个字节整型数据的主机字节序到网络字节序的转换
40 //addr.sin_port=9000;//范围0-65535之间但是0-1024不推荐使用要么被知名协
41 //议使用要么预留出来了
42 //uint32k_t htonl(uint32_t hostshort);
43 addr.sin_port=htons(9000);//实现对主机字节序的判断,判断主机是大端小端是否
44 //需要字节序转换需要则转换,不可以用htonl(转换为4个字节的数据)
45 //uint32k_t htonl(uint32_t hostshort);
46 //4个字节整型数据的主机字节序到网络字节序的转换
//htonl()//长整型的转换
48 //addr.sin_addr.s_addr=htonl(0xc0a87a87);//地址信息,主机字节序小端需要将地址信 息转换为
49 addr.sin_addr.s_addr=inet_addr(“192.168.96.128”);
50 //int_addr_t inet_addr(const char
cp)点分十进制的字符串ip地址直接转换成为网络> 字节序的ip地址
51 //大端,存储长度大于1个字节它是4个字节需要进行转换,9000也是是short类型
52 //unint32_t的数据直接赋值字符串不可以,把点分十进制的ip信息转换为16进制数字
53 //192.168.122.135—>16进制为0xc0 a8 7a 87
54 socklen_t len=sizeof(struct sockaddr_in);
55 int ret=bind(socketfd,(struct sockaddr
)&addr,len);//类型不一样强转,定义的是
56 //sockaddr_in 但是要求是sockaddr*
57 if(ret<0){
58 perror(“bind error\n”);
59 return -1;
60 }
61 while(1){
62 //3接受数据
63 //ssize_t recvfrom(int sockfd, void buf, size_t len, int flags,
64 //struct sockaddr src_addr, socklen_t addrlen);
//sockfd:套接字描述符buf:要接收的数据len要接受的数据长度
66 //flags里面MSG_DONTWAIT使操作变为一个非阻塞操作没有数据则直接报错返回错误
67 //信息为EAGAIN 或者EWOULDBLOCK
68 //flags:0
69 //默认阻塞 MSG_PEEK(从接受队列里找出队首数据进行返回)探测性接受数据
70 //从缓冲区接受数据但是并不从缓冲区移除掉意味着下次接收数据的时候依然
71 //接受的是这条数据–>想接受某种数据的时候探测性的接受一下看数据是否
72 //完整(是不是自己想要的数据)完整则接受不完整则不接收
73 //src_addr接受数据每条数据都有源地址源端口需要知道到底谁发的才能给谁
74 //回就是源地址信息
75 //addrlen源地址信息长度(复合长度必须指定长度,接收多长并返回实际接收长
76 //度)给0接收长度为0不会接收地址信息给太长地址信息地址信息没有这么长
77 //返回真实的地址信息长度(输入输出复合参数)返回值:返回数据的实际接
78 //收数据长度失败:-1
79 char buff[1024]={0};//一定要初始化
80 struct sockaddr_in cli_addr;
81 recvfrom(socketfd,buff,1023,0,(struct sockaddr
)&cli_addr,&len);
82 printf(“client say:%s\n”,buff);
83 //发送数据
84 // ssize_t sendto(int sockfd, const void buf, size_t len, int flags,
//const struct sockaddr dest_addr, socklen_t addrlen);
86 //sickfd:套接字描述符 buf:要发送的数据len要发送的数据长度 flag:0默认阻
87 //塞dest_addr:要发送的对端地址,对于服务端来说都是客户端–>客户端的地址
88 //信息add_rlen地址信息长度返回值:实际发送的字节长度失败:-1
89 memset(buff,0x00,1024);//数据清空
90 scanf("%s",buff);//获取字符串
91 sendto(socketfd,buff,strlen(buff),0,
92 (struct sockaddr
)&cli_addr,len);
93 printf(“sever say:%s\n”,buff);
94 }
95 close(socketfd);
96 return 0;
97 }
客户端程序—c++实现
#include<netinet/in.h>
2 #include
3 #include<sys/socket.h>
4 #include<string.h>
5 #include
6 #include<errno.h>
7 #include<unistd.h>
8 #include<stdio.h>
9 #include<stdlib.h>
10 #include<arpa/inet.h>
//使用c++封装一个udp的socket类
12 //贯穿整个upd的数据是套接字描述符socketfd–>就是私有成员–>句柄
13 class UdpSocket{
14 private:
15 int _sockfd;
16 public:
17 UdpSocket():_sockfd(-1){}//构造函数
18 //1创建套接字
19 bool Socket(){
20 //int socket(int domain, int type, int protocol);AF_INET–>ipv4
21 //参数2:SOCK_STREAM IPPROTO_TCP 6 流式套接字默认协议
22 // SOCK_DGRAM IPPROTO_UDP 17数据报套接字默认udp
23 //
24 _sockfd=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
25 if(_sockfd<0){
26 perror(“socket create errno\n”);
27 return false;
28 }
29 return true;
}
31 //2为套接字保定地址信息//ip地址和端口
32 bool Bind(std::string &ip,uint16_t port){
33 sockaddr_in addr;
34 addr.sin_family=AF_INET;//地址域
35 addr.sin_port=htons(port);//端口
36 addr.sin_addr.s_addr=inet_addr(ip.c_str());//点分十进制转换
37 socklen_t len=(sizeof(sockaddr_in));
38 //bind( int __fd, const struct sockaddr __addr, socklen_t __len )
39 int ret=bind(_sockfd,(sockaddr
)&addr,len);
40 if(ret<0){
41 perror(“bind errno\n”);
42 return false;
43 }
44 return true;
45 }
46 //3接受数据–>返回实际接受的长度
47 ssize_t Recv(char
buf,size_t len,sockaddr_in
addr=NULL){
48 //buf获取发的sockaddr_in获取地址信息–>只要获取数据不需要获取地址信息–
49 //null,len为接受的数据长度
int ret;
51 //recvfrom( int __fd, void __restrict __buf, size_t __n, int __flags, )
52 //套接字描述符,获取的数据放到buf,长度–>指定不合适–>接受数据不知道阻塞
53 //获取地址信息,和地址信息长度
54 sockaddr_in _addr;
55 socklen_t addr_len=sizeof(sockaddr_in);
56 ret=recvfrom(_sockfd,buf,len,0,(sockaddr
)&_addr,&addr_len);
57 //防止addr为空所以要定义
58 if(addr){
59 //addr不为空拷贝长度为addr_len如果想获取addr就会给个地址进来把地址
60 //信息放进去不赋值直接为空不需要拷贝,如果空拷贝会段错误
61 memcpy((void
)addr,(void*)&_addr,addr_len);
62 }
63 return ret;
64 }
65 //4发送数据,地址信息必须指定,要发送给谁(但是接受信息地址信息没必要获取)
66 ssize_t Send(const char*buf,size_t len,sockaddr_in addr){
67 int ret;
68 //0为默认阻塞addr地址信息
69 socklen_t addr_len=(sizeof(sockaddr_in));
ret=sendto(_sockfd,buf,len,0,(sockaddr
)addr,addr_len);
71 return ret;
72 }
73 //5关闭套接字
74 bool IsClose(){
75 if(_sockfd!=-1){
76 close(_sockfd);
77 _sockfd=-1;
78 }
79 return true;
80 }
81 };
82 #define CHECK_RET(q) if((q)==false){return -1;}
83 int main(){
84 UdpSocket sock;//创建套接字
85 CHECK_RET(sock.Socket());
86 sockaddr_in serv_addr;//服务端的地址
87 serv_addr.sin_family=AF_INET;
88 serv_addr.sin_port=htons(9000);
89 serv_addr.sin_addr.s_addr=inet_addr(“192.168.96.128”);
while(1){
91 //绑定数据有操作系统自己绑定,然后发数据
92 //std::string str;
93 //std::cin>>str;
94 //sock.Send(str.c_str(),str.length(),&serv_addr);//str.const_str
95 //std::cout<<“client say:”<<str<<std::endl;
96 char buff[1024]={0};
97 scanf("%s[^\n]",buff);//获取一行
98 sock.Send(buff,strlen(buff),&serv_addr);
99 printf(“client say :%s\n”,buff);
100 //接收
101 char buf[1024]={0};
102 sock.Recv(buf,1024);//地址可以不需要
103 printf(“server buf:%s\n”,buf);
104 }
105 sock.IsClose();
106 return 0;
107 }
linux套接字--udp协议实现网络通信
如果允许出现这样情况是因为此处ip地址必须是自己本机的
linux套接字--udp协议实现网络通信
从而在上图找到自己的主机ip地址192.168…
开两个终端第一个为用户端用户端说你好,服务端收到回复enen
结果如下
客户端界面为:
linux套接字--udp协议实现网络通信
则服务端为:服务端接受到然后又发送heihei则用户端收到然后继续发送enna
linux套接字--udp协议实现网络通信

相关文章: