1、网络编程基础
IP地址
在IP数据包头部中,有两个IP地址,分别是源IP地址和目的IP地址。
端口号
端口号(port)是传输层协议的内容。端口号是一个2字节16位的整数,用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。IP地址 + 端口号能够标识网络上的某一台主机的某一个进程。一个端口号只能被一个进程占用,一个进程可以绑定多个端口号。传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号。
UDP协议
传输层协议、无连接、不可靠传输、面向数据报
TCP协议
传输层协议、面向连接、可靠传输、面向字节流
2、网络编程接口
网络字节序
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
- 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
- 网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址
- TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据,如果当前发送主机是小端,就需要先将数据转成大端,否则就忽略,直接发送即可
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
/*
*h:host
*n:network
*l:long32
*s:short16
*/
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
Socket编程接口
Socket常见API
//创建socket文件描述附(TCP/UDP,Client/Server)
int socket(int domain, int type, int protocol);
//绑定端口号(TCP/UDP,Server)
int bind(int socket, const struct sockaddr * address,
socklen_t address_len);
//开始监听socket(TCP,Server)
int listen(int socket, int backlog);
//接收请求(TCP,Server)
int accept(int socket, struct sockaddr * address,
socklen_t * address_len);
//建立连接(TCP,Client)
int connect(int sockfd, const struct sockaddr * addr,
socklen_t addrlen);
sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6以及UNIX DomainSocket。然而,各种网络协议的地址格式并不相同。
- struct sockaddr:16位地址类型+14字节地址数据
- struct sockaddr_in:16位地址类型AF_INET,16位端口号,32位IP地址,8字节填充
- struct sockaddr_un:16位地址类型AD_UNIX,108字节路径名
注:
- IPv4和IPv6的地址格式定义在<netinet/in.h>中,IPv4地址用sockaddr_in结构体表示。
- IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6。只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。
- socket API可以都用struct sockaddr *类型表示,在使用的时候需要强制转化成sockaddr_in,这样的好处是程序的通用性,可以接收IPv4、IPv6以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。
sockaddr结构
struct sockaddr
{
__SOCKADDR_COMMON (sa_);/*Common data:address family and length.*/
char sa_data[14];/*Address data.*/
};
sockaddr_in结构
struct sockaddr_in
{
__SOCKADDR_COMMON (sin_);
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[sizeof(struct sockaddr)-__SOCKADDR_COMMON_SIZE-sizeof(in_port_t)-sizeof(struct in_addr)];
};
虽然socket API的接口是sockaddr,但是我们真正在基于IPv4编程时,使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、IP地址。
in_addr结构
typedef uint32_t in_addt_t;
struct in_addr
{
in_addr_t s_addr;
};
3、基于UDP的网络套接字编程
UDP Socket封装
#pragma once
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<cassert>
#include<string>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
typedef struct sockaddr sockaddr;
typedef struct sockaddr_in sockaddr_in;
class udp_socket
{
private:
int _fd;
public:
udp_socket():_fd(-1)
{}
bool _socket()
{
_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(_fd < 0)
{
perror("socket error");
return false;
}
return true;
}
bool _close()
{
close(_fd);
return true;
}
bool _bind(const std::string& ip, uint16_t port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
int ret = bind(_fd, (sockaddr *)&addr, sizeof(addr));
if(ret < 0)
{
perror("bind");
return false;
}
return true;
}
bool _recvfrom(std::string * buf, std::string * ip = nullptr, uint16_t * port = nullptr)
{
char tmp[1024 * 10] = {};
sockaddr_in peer;
socklen_t len = sizeof(peer);
ssize_t read_size = recvfrom(_fd, tmp, sizeof(tmp) - 1, 0, (sockaddr *)&peer, &len);
if(read_size < 0)
{
perror("recvfrom");
return false;
}
buf->assign(tmp, read_size);
if(ip != nullptr)
{
*ip = inet_ntoa(peer.sin_addr);
}
if(port != nullptr)
{
*port = ntohs(peer.sin_port);
}
return true;
}
bool _sendto(const std::string& buf, const std::string& ip, uint16_t port)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(ip.c_str());
addr.sin_port = htons(port);
ssize_t write_size = sendto(_fd, buf.data(), buf.size(), 0, (sockaddr *)&addr, sizeof(addr));
if(write_size < 0)
{
perror("sendto");
return false;
}
return true;
}
};
UDP通用服务器
#pragma once
#include"udp_socket.hpp"
#include<functional>
typedef std::function<void (const std::string&, std::string* resp)> Handler;
class udp_server
{
private:
udp_socket u_sock;
public:
udp_server()
{
assert(u_sock._socket());
}
~udp_server()
{
u_sock._close();
}
bool _start(const std::string& ip, uint16_t port, Handler handler)
{
if(!u_sock._bind(ip, port))
{
return false;
}
while(true)
{
std::string req;
std::string remote_ip;
uint16_t remote_port = 0;
if(!u_sock._recvfrom(&req, &remote_ip, &remote_port))
{
continue;
}
std::string resp;
handler(req, &resp);
u_sock._sendto(resp, remote_ip, remote_port);
printf("[%s:%d] req:%s, resp:%s\n", remote_ip.c_str(), remote_port, req.c_str(), resp.c_str());
}
u_sock._close();
return true;
}
};
UDP通用客户端
#pragma once
#include"udp_socket.hpp"
class udp_client
{
public:
udp_client(const std::string& ip, uint16_t port):_ip(ip),_port(port)
{
assert(u_sock._socket());
}
~udp_client()
{
u_sock._close();
}
bool _recvfrom(std::string* buf)
{
return u_sock._recvfrom(buf);
}
bool _sendto(const std::string& buf)
{
return u_sock._sendto(buf, _ip, _port);
}
private:
udp_socket u_sock;
std::string _ip;
uint16_t _port;
};
3、英译汉词典的实现
服务端
#include"udp_server.hpp"
#include<iostream>
#include<unordered_map>
using namespace std;
std::unordered_map<std::string, std::string> g_dict;
void Translate(const std::string& req, std::string* resp)
{
auto it = g_dict.find(req);
if(it == g_dict.end())
{
*resp = "Not Fount!";
return;
}
*resp = it->second;
}
void Dict()
{
g_dict.insert(std::make_pair("template", "模板"));
g_dict.insert(std::make_pair("iterator", "迭代器"));
g_dict.insert(std::make_pair("unorder", "无序"));
g_dict.insert(std::make_pair("array", "数组"));
g_dict.insert(std::make_pair("database", "数据库"));
g_dict.insert(std::make_pair("insert", "插入"));
g_dict.insert(std::make_pair("delete", "删除"));
g_dict.insert(std::make_pair("update", "修改"));
g_dict.insert(std::make_pair("select", "查询"));
g_dict.insert(std::make_pair("string", "字符串"));
}
int main(int argc, char * argv[])
{
if(argc != 3)
{
cout<<"Use Format './dict_server [IP] [PORT]'"<<endl;
return 1;
}
Dict();
udp_server server;
server._start(argv[1], atoi(argv[2]), Translate);
return 0;
}
客户端
#include"udp_client.hpp"
#include<iostream>
using namespace std;
int main(int argc, char * argv[])
{
if(argc != 3)
{
cout<<"Use Format './dict_client [IP] [PORT]'"<<endl;
return 1;
}
udp_client client(argv[1], atoi(argv[2]));
while(true)
{
string word;
cout<<"Input a word you would find:";
cin>>word;
if(strcmp(word.c_str(), "exit") == 0)
{
char sure[5];
cout<<"Are you sure exit?[Y/N]";
cin>>sure;
if(strcmp(sure, "Y") == 0)
{
break;
}
else
{
continue;
}
}
client._sendto(word);
string res;
client._recvfrom(&res);
cout<<word<<" means "<<res<<endl;
}
return 0;
}