源ip地址和目的ip地址
我们先来看个例子:
如果当女儿国国王问你上一站从何而来,下一站去往何处?唐僧就会说我上一站从XXX来下一站到XXX。唐僧总是有2套说辞。源ip地址就像是唐僧的上一站,目的ip就是下一站的地址。
源ip地址:就是发送数据包的那个电脑的IP地址。
目的ip地址: 就是想要发送到的那个电脑的IP地址。
端口号
那我们有了ip地址就能通信了吗?例如QQ发消息,我们有了ip地址能够把信息发给对方的机器上,但是我们还需要有一个其他的标识来区分出这个数据交给哪个程序来进行解析。
下面来简单认识一下端口号:
ip来标识主机,端口号标识进程,ip+端口号就可以标识全网的唯一进程
,我们就可以知道数据要交给哪个程序解析了。一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定。
源端口号和目的端口号
和上面的源ip地址、目的ip地址一样的,传输层协议的数据段中有2个端口号,源端口号:数据是谁发的,目的端口号:要发给谁
udp协议:先简单认识一下,后面有详细的讲解
内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
下面有一批接口可以使用:
#include
uint32_t htonl(uint32_t hostlong);
uint16_t htonl(uint16_t hostshort);
uint32_t htonl(uint32_t netlong);
uint16_t htonl(uint16_t netshort);
详细解释:
先来认识udp的一套接口:
1.创建socket
int socket(int domain, int type, int protocol);
参数说明:
domain:协议域又称协议家族,协议族决定了socket的地址类型,我们使用ipv4进行通信,使用AF_INET
type:套接字类别,有流式套接字和数据报套接字,upd使用的是SOCK_DGRAM
protocol:协议指定与套接字一起使用的特定协议。默认使用0即可。
返回值:
为什么返回文件描述符?
Linux中说一切皆文件,为了表示和区分已经打开的文件,Linux 会给每个文件分配一个 ID,这个 ID 就是一个整数,被称为文件描述符(File Descriptor)。例如:
通常用 0 来表示标准输入文件(stdin),它对应的硬件设备就是键盘;
通常用 1 来表示标准输出文件(stdout),它对应的硬件设备就是显示器。
Linux 程序在执行任何形式的 I/O 操作时,都是在读取或者写入一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。
所以网络连接也是一个文件,也要有文件描述符
,所以它的返回值是文件描述符。
2.bind(绑定函数)
函数原型:
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
参数说明:
socket:需要绑定的socket
addr:存放了服务端用于通信的地址和端口。
addrlen:表示addr结构体的大小。
返回值:成功返回0,失败返回-1
3.recvfrom(接收)
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr*src_addr, socklen_t *addrlen);
参数说明:
注意后两个参数是输出参数,其中addrlen既是输入又是输出参数,即值-结果参数,需要在调用时,指明src_addr的长度。另外,如果不关心数据发送端的地址,可以将后两者均设置为NULL。
返回值:如果正确接收返回接收到的字节数,失败返回-1.
4.sendto(发送数据)
函数原型:
ssize_t sendto(int sockfd, const void *buf, size_t len,int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
参数说明:
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要的UNIX DomainSocket. 然而, 各种网络协议的地址格式并不相同。上面的接口都是使用sockaddr结构,先来看一下它的结构:
它们的接口使用的都是struct sockaddr,是为了使用一套接口就可以完成通信。但是我们在使用的时候使用的是第2个,因为我们实现通信就要传ip地址和端口号,哪个8字节填充是为了内存对齐,不用管它。那怎么区别它们呢?
只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好
处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
sockaddr_in结构
in_addr结构
in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数。
知道了上面的接口,下面我们使用这些接口写一个简单的udp通信。
服务器端:
我们让服务器一直读客户端发来的信息,并给服务器返回。整体的逻辑:先创建套接字,绑定,接收客户端信息,给客户端返回信息。
udpServer.hpp:
1 #pragma once
2 #include<iostream>
3 #include<cstdio>
4 #include<string>
5 #include<unistd.h>
6 #include<sys/socket.h>
7 #include<stdlib.h>
8 #include<sys/types.h>
9 #include<netinet/in.h>
10 #include<arpa/inet.h>
11
12 class udpServer
13 {
14 private:
15 std::string ip;
16 int port;
17 int sock;
18 public:
19 udpServer(std::string _ip="127.0.0.1",int _port = 8088)
20 :ip(_ip)
21 ,port(_port)
22 {}
23 void initServer()
24 {
25 sock = socket(AF_INET,SOCK_DGRAM,0);
26 std::cout<<"sock:"<<sock<<std::endl;
27 struct sockaddr_in local;
28 local.sin_family = AF_INET;
29 local.sin_port = htons(port);//主机序列转成网络序列
30 local.sin_addr.s_addr = inet_addr(ip.c_str());
31
32 //开始绑定
33 //绑定失败就直接退出
34 if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0)
35 {
36 std::cerr<<"bind error"<<std::endl;
37 exit(1);
38
39 }
40 }
41 void start()
42 {
43 char msg[64];
44 for(;;)
45 {
46 msg[0] = '\0';
47 //从远端接收
48 struct sockaddr_in end_point;
49 socklen_t len = sizeof(end_point);
50 ssize_t s = recvfrom(sock,msg,sizeof(msg)-1,0,(struct sockaddr*)&end_point,&len);
51 if(s > 0 )
52 {
53 msg[s] = '\0';
54 std::cout<<"client:"<<msg<<std::endl;
55 std::string echo_string = msg;
56 echo_string += "[srever echo]";
57 sendto(sock,echo_string.c_str(),echo_string.size(),0,
58 (struct sockaddr*)&end_point,len);
59 }
60 }
61 }
62 ~udpServer()
63 {
64 close(sock);
65 }
66 };
udpServer.cc
1 #include"udpServer.hpp"
2
3 int main()
4 {
5 udpServer *us = new udpServer();
6 us->initServer();
7 us->start();
8 delete us;
9 return 0;
10 }
客户端发消息给服务器端,服务器端返回echo server
udpClient,hpp
1 #pragma once
2 #include<iostream>
3 #include<cstdio>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<stdlib.h>
7 #include<sys/types.h>
8 #include<arpa/inet.h>
9 #include<string>
10 #include<netinet/in.h>
11 class udpClient
12 {
13 private:
14 std::string ip;
15 int port;
16 int sock;
17 public:
18 udpClient(std::string _ip="127.0.0.1",int _port = 8088)
19 :ip(_ip)
20 ,port(_port)
21 {}
22 void initClient()
23 {
24 sock = socket(AF_INET,SOCK_DGRAM,0);
25 std::cout<<"sock:"<<sock<<std::endl;
26
27 }
28 void start()
29 {
30
31 std::string msg;
32 struct sockaddr_in peer;
33 peer.sin_family = AF_INET;
34 peer.sin_port = htons(port);
35 peer.sin_addr.s_addr = inet_addr(ip.c_str());
36 for(;;)
37 {
38 std::cout<<"please enter:";
39 std::cin>>msg;
40 if(msg == "quit")
41 {
42 break;
43 }
44 sendto(sock,msg.c_str(),msg.size(),0,(struct sockaddr*)&peer,sizeof(peer));
45 char echo[128];
46 ssize_t s = recvfrom(sock,echo,sizeof(echo)-1,0,nullptr,nullptr);
47 if(s > 0)
48 {
49 echo[s] = 0;
50 std::cout<<"server:"<<echo<<std::endl;
51 }
52 }
53 }
54 ~udpClient()
55 {
56 close(sock);
57 }
58 };
udpClient.cc
1 #include"udpClient.hpp"
2
3 int main()
4 {
5 udpClient uc;
6 uc.initClient();
7 uc.start();
8 return 0;
9 }
下面开始通信
我们可以看到通信成功了,服务器给我们返回echo server。
12 class udpServer
13 {
14 private:
15 // std::string ip;
16 int port;
17 int sock;
18 public:
19 udpServer(int _port = 8088)
20 //:ip(_ip)
21 :port(_port)
22 {}
23 void initServer()
24 {
25 sock = socket(AF_INET,SOCK_DGRAM,0);
26 std::cout<<"sock:"<<sock<<std::endl;
27 struct sockaddr_in local;
28 local.sin_family = AF_INET;
29 local.sin_port = htons(port);//主机序列转成网络序列
30 //local.sin_addr.s_addr = inet_addr(ip.c_str());
31 local.sin_addr.s_addr = INADDR_ANY;
将sin.addr设为INADDR_ANY;
运行服务器:
用命令:netstat -nlup
查看udp协议相关的统计数据,一般用于检验本机各端口的网络连接情况
转换过来就是0.0.0.0,泛指本机的意思,也就是表示本机的所有IP,因为有些机子不止一块网卡,多网卡的情况下,这个就表示所有网卡ip地址的意思。所以出现INADDR_ANY,你只需绑定INADDR_ANY,管理一个套接字就行,不管数据是从哪个网卡过来的,只要是绑定的端口号过来的数据,都可以接收到。
客户端和服务器端都加上命令行参数
服务器端:
1 #include"udpServer.hpp"
2
3 void Usage(std::string proc)
4 {
5 std::cout<<"Usage: "<<proc<<"port"<<std::endl;
6 }
7 int main(int argc,char* argv[])
8 {
9 if(argc != 2)
10 {
11 Usage(argv[0]);
12 exit(1);
13 }
14 udpServer *us = new udpServer(atoi(argv[1]));
15 us->initServer();
16 us->start();
17 delete us;
18 return 0;
19 }
客户端:
1 #include"udpServer.hpp"
2
3 void Usage(std::string proc)
4 {
5 std::cout<<"Usage: "<<proc<<"port"<<std::endl;
6 }
7 int main(int argc,char* argv[])
8 {
9 if(argc != 2)
10 {
11 Usage(argv[0]);
12 exit(1);
13 }
14 udpServer *us = new udpServer(atoi(argv[1]));
15 us->initServer();
16 us->start();
17 delete us;
18 return 0;
19 }
效果演示:
可以绑定127.0.0.1本地环回网,和ifconfig查看的ip都可以进行通信。
小结:
客户端不需要绑定
。为什么呢?
本地环回:
通常用来进行网络通信代码的本地测试,一般跑通,本地环境以及代码基本没有问题。
也可以远程通信,代码链接,点击直达。
有客户端代码就可以和博主远程通信了。