linux 套接字编程
tcp/udp
tcp --传输控制协议 --面向连接,可靠传输,面向字节流–保证可靠传输(保证数据能够安全有序传递 、传输速度没有udp快)
udp— 用户数据报协议 --面向无连接 ,不可靠,面向数据报。实时性很快 安全性比较低。
udp通信流程
服务端(被动)
1.创建套接字
在内核中创建socket结构体
2.为套接字绑定地址信息
主要绑定 协议信息 绑定地址 端口号
3.接收数据
告诉操作系统 应该处理哪个地址和端口号的数据
操作系统接受到消息后会放到socket接收缓冲区中这一步便是将数据取出
注:每条数据一般都会有5条信息 sip:sport dip:dport protocol 源ip与端口 目的ip与端口
4.发送数据
讲数据写入内核中的socket发送缓冲区,操作系统选择合适的时候将数据封装发送出去。
5. 关闭套接字
udp通用服务端
udp_server.hpp
#pragma once
#include "udp_socket.hpp"
// C 式写法
// typedef void (*Handler)(const std::string& req, std::string* resp);
// C++ 11 式写法, 能够兼容函数指针, 仿函数, 和 lambda
#include <functional>
typedef std::function<void (const std::string&, std::string* resp)> Handler;
class UdpServer {
public:
UdpServer() {
assert(sock_.Socket());
}
~UdpServer() {
sock_.Close();
}
bool Start(const std::string& ip, uint16_t port, Handler handler) {
// 1. 创建 socket
// 2. 绑定端口号
bool ret = sock_.Bind(ip, port);
if (!ret) {
return false;
}
// 3. 进入事件循环
for (;;) {
// 4. 尝试读取请求
std::string req;
std::string remote_ip;
uint16_t remote_port = 0;
bool ret = sock_.RecvFrom(&req, &remote_ip, &remote_port);
if (!ret) {
continue;
}
std::string resp;
// 5. 根据请求计算响应
handler(req, &resp);
// 6. 返回响应给客户端
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());
}
sock_.Close();
return true;
}
private:
UdpSocket sock_;
};
以上代码是对 udp 服务器进行通用接口的封装. 基于以上封装, 实现一个查字典的服务器就很容易了dict_server.cc
#include "udp_server.hpp"
#include <unordered_map>
#include <iostream>
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 = "未查到!";
return;
}
*resp = it->second;
}
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./dict_server [ip] [port]\n");
return 1;
}
// 1. 数据初始化
g_dict.insert(std::make_pair("hello", "你好"));
g_dict.insert(std::make_pair("world", "世界"));
g_dict.insert(std::make_pair("c++", "最好的编程语言"));
g_dict.insert(std::make_pair("bit", "特别NB"));
// 2. 启动服务器
UdpServer server;
server.Start(argv[1], atoi(argv[2]), Translate);
return 0;
}
客户端(主动)
1.创建套接字
int socket(int domain ,int type ,int protocol)
1.domain:
地址域—确定这个socket使用哪种协议版本的地址域)
2.地址域:
AF_INET(ipv4) AF_INET6(ipv6) AF_PACKET(直接获取以太网的数据包)其他的这里不做介绍
3.type 套接字:
SOCK_STREAM(TCP用的流式套接字)
SOCK_DGRAM(udp用的数据报类型套接字)
4.protocol 传输层协议选择 :
IPPRORO_IP 0
IPPRORO_ICMP 1
IPPRORO_IGMP 2
···
IPPRORO_TCP 6
···
IPPRORO_UDP 17
上面这个了解就行 是底层的枚举
当套接字选择SOCK_STREAM/SOCK_DGRAM 这里传0默认是tcp/udp协议
5.返回值 :
文件描述符(套接字描述符):一个非负整数、是套接字其他所有接口的操作句柄、失败返回-1;
2.为套接字绑定地址信息
为了降低端口冲突的概率客户端不推荐主动绑定地址端口
服务端为什么要绑定,绑定ip客户端才能找到你 绑定端口号才能找到你的进程 一个进程可以有多个端口号但是一个端口号只能有一个进程
int bind(int sockfd ,struct sockaddr *addr ,socklen_t len);
1.sockfd:套接字的句柄 就是第一步返回的那个
2.add要绑定的地址信息
struct sockaddr结构体类型如下
这里再了解一下struct sockaddr_in
struct sockaddr { sa_family_t sa_family; char sa_data[14]; } struct sockaddr_in{ sa_family_t sin_family; 地址域 in_port_t sin_port;端口号 struct in_addr{in_addr_t s_addr}sin_addr; //ip地址in_addr_t结构体下面是一个32位无符号整形具体就不列举了 }
1.sockaddr 是一个通用的网络结构体
sa_family_t 是一个无符号16位整形
2.sockaddr_in
sa_family_t sin_family;(AF_INET)
还有很多结构体 struct sockaddr_un等等 但是都是干一件事情将 地址端口等信息存储 只不过方式不同 定义地址结构的时候 要使用协议规定的地址结构 如ipv4使用 struct sockaddr_in
加入用的通用的结构体
如 addr->sa_family ==AF_INET 按照ipv4去解析
3.发送数据
就是讲数据放到socket的发送缓冲区,操作系统选择合适的时机自动发送
若socket 发送数据的时候还没有绑定地址则操作系统自动选择合适的地址发送
ssize_t sendto (int sockfd, char data,int len ,int flag,struct sockaddrpeer_addr ,socklen_t addrlen)
1.socket操作句柄
2.data 要发送的数据首地址
3.要发送的数据长度
4.flag:默认为0表示阻塞操作
5.peer_addr :接收方的地址信息
6.addrlen:地址信息长度
7.成功返回发送的字节长度失败返回-1
4.接受数据
ssize_t recvfrom(int sockfd ,char *buf ,int len ,int flag ,struct sockaddr *peer_addr , socklen_t *addrlen)
1.sockfd 句柄
2.buf 用于接收数据
- len想要接收的数据长度
- flag:操作选项标志 , 默认为0 ,表示阻塞操作 5.peer_addr:发送方的地址信息
- addrlen:想要获取地址信息的长度以及返回的实际长度
- 返回实际接收的字节长度,失败返回-1
5.关闭套接字
udp通用客户端
udp_client.hpp
#pragma once
#include "udp_socket.hpp"
class UdpClient {
public:
UdpClient(const std::string& ip, uint16_t port) : ip_(ip), port_(port) {
assert(sock_.Socket());
}
~UdpClient() {
sock_.Close();
}
bool RecvFrom(std::string* buf) {
return sock_.RecvFrom(buf);
}
bool SendTo(const std::string& buf) {
return sock_.SendTo(buf, ip_, port_);
}
private:
UdpSocket sock_;
// 服务器端的 IP 和 端口号
std::string ip_;
uint16_t port_;
};
实现英译汉客户端
#include "udp_client.hpp"
#include <iostream>
int main(int argc, char* argv[]) {
if (argc != 3) {
printf("Usage ./dict_client [ip] [port]\n");
return 1;
}
UdpClient client(argv[1], atoi(argv[2]));
for (;;) {
std::string word;
std::cout << "请输入您要查的单词: ";
std::cin >> word;
if (!std::cin) {
std::cout << "Good Bye" << std::endl;
break;
}
client.SendTo(word);