之前说了多路转接,那么今天我来谈谈我对于网络编程套接字的理解。
初识ip地址和端口号
ip地址
ip地址有两个版本:ipv4和ipv6,我在这里介绍ipv4:
1.ip地址是在ip协议中,用来标识网络中不同主机的地址,它分为两部分:
2.对于ipv4来说,ip地址是一个4字节32位的整数;
3.人们通常用点分十进制的字符串表示ip地址,比如127.0.0.1(每一个用点分隔的数字代表一字节)。
端口号
可是说,网络通信的本质也是进程间通信,所以虽然ip地址能够把数据发到对端机器上,但还需要有一个标识来区分出这个数据要给那个进程来解析,这个标识就是端口号。
1.端口号是个2字节16为整数,是传输层协议的内容;
2.用来标示一个进程,告诉内核数据要交给哪个进程处理;
3.ip地址+端口号能标识网络上唯一主机的唯一一个进程;
4.一个端口号只能被一个进程占据。
在这里需要注意的一点是,进程id也表示了一个唯一的进程,要把端口号与进程id区分清楚:进程应用于网络才有端口号。
举个例子,我们打10086就相当于是ip地址,而转人工客服就相当于端口号。
初识传输层的两个协议
这两个协议我在后面的文章将要详细的说明,在这里先简单说一下,便于后面实现代码:
UDP协议:无连接,不可靠,面向数据报。
TCP协议:有连接,很可靠,面向字节流。
在这里需要注意面向数据报和面向字节流的区别:
UDP协议是面向数据报的,也就是说读的时候只能整包整包的读,不能读半包,一包半之类的;
而TCP协议是面向字节流的,想读多少就读多少。
通过这些特点,可以看出来TCP明显功能上是优于UDP的,但是UDP有一点就是简单。
网络字节序
内存中多字节数据相对于内存地址有大小端之分;磁盘文件中的多字节数据相对于文件中的偏移地址也有大小端之分;同样,网络数据流同样有大小端之分。
为了统一,TCP/IP规定,网络数据流应采用大端,即低地址高字节。
具体细节就是,如果发送端是小端,就需要先将数据转为大端再发送;如果发送端是大端,直接发送。
可以调用下面的库函数进行网络字节序和主机字节序的转换:
函数名是见名知意的,拿第一个举例:
我们在编写套接字时,常常利用htonl函数将端口号的主机序转为网络字节序。
常见的socket接口
这些函数具体的介绍,有一个大神写得很好,附上链接:
https://blog.youkuaiyun.com/y396397735/article/details/50655363
sockaddr结构
sockaddr和 void* 类似,是为了泛型处理不同的套接,适用于各种底层不同地址格式的网络协议,比如ipv4,ipv6等。
如图,不同的网络协议的底层不同;
对于ipv4,地址类型为AF_INET,对于ipv6,地址类型为AF_INET6,只要取得某sockaddr结构体的16为地址类型,不需要知道到底是哪个,就能根据地址类型确定结构体的内容。
在使用socket API的时候,需要强转为sockaddr,这样做增强了程序的通用性。
sockaddr结构
sockaddr_in结构
基于ipv4编程,使用的正是sockaddr_in,这个结构体主要三部分:地址类型,端口号,ip地址。
地址转换函数
基于以上的认知,实现一个简单UDP网络通信:
1.对于UDP,socket的参数使用SOCK_DGRAM;
2.UDP不进行连接,直接使用sendto和recvfrom来收发数据。
服务器:
int main(int argc,char* argv[])
{
//采用./server [ip] [port]的形式传入ip地址和端口号
if(argc != 3){
printf("./server [ip] [port]\n");
return 1;
}
// ipv4 UDP
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return 2;
}
//构造出地址协议加ip地址加端口号
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(argv[1]);
local.sin_port = htons(atoi(argv[2]));
//绑定端口号
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
return 3;
}
while(1){
char buf[1024];
struct sockaddr_in client;
socklen_t len = sizeof(client);
//UDP不建立连接,直接对数据进行读写
ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&client,&len);
if(s > 0){
buf[s] = 0;
printf("client> :%s\n",buf);
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&client,sizeof(client));
}
}
return 0;
}
客户端:
int main(int argc,char* argv[])
{
if(argc != 3){
printf("./client [ip] [port]\n");
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
perror("socket");
return 2;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
//UDP无需建立连接,直接进行数据传输
while(1){
char buf[1024] = {0};
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t s = read(0,buf,sizeof(buf)-1);
if(s > 0){
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&addr,sizeof(addr));
ssize_t s2 = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(s2 > 0){
buf[s2] = 0;
printf("#> :%s\n",buf);
}
}
}
return 0;
}
来看看运行效果:
服务器收到来自客户端的数据,输出并回显给客户端。
编写简单的TCP网络通信
服务器:
int main(int argc,char* argv[])
{
if(argc != 3){
printf("./server [ip] [port]\n");
return 1;
}
int listen_sock = socket(AF_INET,SOCK_STREAM,0);
if(listen_sock < 0){
perror("listen_sock");
return 2;
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_addr.s_addr = inet_addr(argv[1]);
local.sin_port = htons(atoi(argv[2]));
if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) < 0){
perror("bind");
return 3;
}
if(listen(listen_sock,5) < 0){
perror("listen");
return 4;
}
while(1){
struct sockaddr_in client;
socklen_t len = sizeof(client);
int new_sock = accept(listen_sock,(struct sockaddr*)&client,&len);
if(new_sock < 0){
perror("accept");
return 5;
}
while(1){
char buf[1024] = {0};
ssize_t s = read(new_sock,buf,sizeof(buf));
if(s < 0){
perror("read");
break;
}
else if(s == 0){
printf("客户端已退出,可以关闭该连接");
close(new_sock);
break;
}
else{
printf("client: %s\n",buf);
}
}
}
return 0;
}
客户端:
int main(int argc,char* argv[])
{
if(argc != 3){
printf("./client [ip] [port]\n");
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
perror("socket");
return 2;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
if(connect(sock,(struct sockaddr*)&addr,sizeof(addr)) < 0){
perror("connect");
return 3;
}
while(1){
char buf[1024] = {0};
ssize_t s = read(0,buf,sizeof(buf));
if(s > 0){
ssize_t s2 = write(sock,buf,strlen(buf));
}
}
return 0;
}