网络编程套接字
认识socket套接字
理解源IP地址和目的IP地址
我们在 初识计算机网络 中也提到了源IP地址和目的IP地址,这里我们再来深入理解一下这个概念
- 在IP数据包的头部,有两个IP地址,一个源IP地址,一个目的IP地址,它代表着这个数据包从哪里来准备去往哪里
- 举个例子来说,西游记大家不陌生吧,唐僧西天取经,每到一个地方有人问唐僧你从哪里来准备去哪,唐僧回答的都是 “贫僧从东土大唐而来,去往西天取经”,那我们就知道了,唐僧的源地址是东土大唐,目的地址是西天,这是一直都不会变的,我们说的源IP地址和目的IP地址也是一样的道理
- 但是我们想想我们是不是有一个IP地址之后就能够完成通信了呢,就像唐僧到西天是不是就可以取经了呢,答案很明显是不是,那唐僧到了西天,应该具体到西天的哪才能取到真经,就像我们应该将信息发到目的IP地址处的哪台主机上的哪个进程才能被接收呢?
认识端口号
端口号是传输层协议的内容
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
- IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
- 一个端口号只能被一个进程占用.
公网IP标识互联网中的唯一一台主机,端口号用来标识一个主机上的唯一一个网络进程
区别端口号与进程PID
我们刚刚说到端口号用来标识一个唯一的进程,我们之前在系统编程进程部分时也学到进程PID用来标识一个唯一的进程,那二者有什么区别呢
- 进程PID:只要是一个进程都有进程PID
- 端口号: 只有网络进程才有端口号,即需要使用网络的进程才会有端口号
认识UDP
此处我们先对TCP(Transmission Control Protocol 传输控制协议)有一个直观的认识; 后面我们再详细讨论TCP的一 些细节问题.
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
认识UDP协议
此处我们也是对UDP(User Datagram Protocol 用户数据报协议)有一个直观的认识; 后面再详细讨论.
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
网络字节序
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分(大端:高位存在高地址,低位存在低地址 小端:高位存低地址,低位存高地址), 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;
- 因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址.
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.
- 不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;
- 如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
网络字节序与主机字节序的转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回
- 如果主机是大端字节序,函数不做转换,将参数原封不动的返回
socket编程接口
sokcet常见API
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
sockaddr结构
- IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16位端口号和32位IP地址.
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.
- socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数
简单的UDP服务器
- UDPServer.cc
#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(string proc)
{
cerr << proc << "IP PORT" << endl;
cerr <<"Version:0.0.1" << endl;
}
//udpServer ip port
int main(int argc,char* argv[])
{
if(argc != 3){
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
cerr << "socket error" << endl;
return 2;
}
cout << "sock: " << sock << endl;
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(atoi(argv[2]));
local.sin_addr.s_addr = inet_addr( argv[1]);
if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){
cerr << "bind error" << endl;
return 3;
}
char buf[1024];
for(;;){
struct sockaddr_in peer;
socklen_t len = sizeof(peer);//!!!
ssize_t s = recvfrom(sock,buf,sizeof(buf)-1,0,(struct sockaddr*)&peer,&len);
if(s > 0){
buf[s] = 0;//
cout << "client# "<< buf << endl;
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&peer,len);
}
}
close(sock);
return 0;
}
- UDPClient.cc
#include <iostream>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
void Usage(string proc)
{
cerr << proc << "SERVER_IP SERVER_PORT" << endl;
cerr <<"Version:0.0.1" << endl;
}
//udpClient serverip serverport
int main(int argc,char* argv[])
{
if(argc != 3){
Usage(argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock < 0){
cerr << "socket error" << endl;
return 2;
}
cout << "sock: " << sock << endl;
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
//client is not to bind port
char buf[1024];
for(;;){
cout << "Please Enter# ";
cin >> buf;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);//!!!
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&server,sizeof(server));
ssize_t s = recvfrom(sock, buf, sizeof(buf)-1, 0,(struct sockaddr*) &peer,&len);
if(s > 0){
buf[s] = 0;//
cout << "server echo# "<< buf << endl;
sendto(sock,buf,strlen(buf),0,(struct sockaddr*)&peer,len);
}
}
close(sock);
return 0;
}
上述是一个简单的基于UDP协议的服务器,下面链接中有基于TCP协议的服务器,感兴趣的读者可以自行参考
项目源码: https://github.com/CXYhh121/Linux.git