目录
1.预备知识
1.1.理解源IP地址和目的IP地址
1.2.认识端口号
1.2.1.理解"端口号"和"进程ID"
1.2.2.理解源端口号和目的端口号
1.3.认识TCP/UDP协议
1.3.1.TCP协议
1.3.2.UDP协议
1.4.网络字节序
网络字节序和主机字节序的转换
2.socket编程接口
2.1.sockaddr结构
struct sockaddr_in 的具体结构:
2.2.socket常见API
2.3.点分十进制到init32_t之间的转化的原理
2.3.1. 12345 ---> “27.48.0.0”
2.3.2. “27.48.0.0” ---> 12345
2.4.查看网络情况
2.4.1.netstat命令
2.5.udp协议实现网络字典
编辑
2.6.远端的shell解释器
2.7.udp——实现网络聊天室
2.8.Windows版本的的网络套接字
1.预备知识
1.1.理解源IP地址和目的IP地址
在IP数据包头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。确定的在路由期间的方向性。为转发的每一个阶段提供方向。
1.2.认识端口号
思考:我们光有IP地址就可以完成通信了嘛?想象一下发qq消息的例子,有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析.你怎么知道这个数据段给qq还是微信。
端口号(port)是传输层协议的内容.
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理;
- IP地址标识一台唯一的主机。
- 端口号标识主机上唯一的一个进程。
- IP地址+端口号能够标识网络上的某一台主机的某一个进程(标识全网唯一的进程)。
- 一个端口号只能被一个进程占用.
网络通讯的本质就进程间通讯。也是进程到进程之间的通讯。两个进程都要看到同一份资源--网络。通讯就是在做IO(收数据或者发送数据)。
1.2.1.理解"端口号"和"进程ID"
我们之前在学习系统编程的时候,学习了pid表示唯一一个进程;此处我们的端口号也是唯一表示一个进程.那么这两者之间是怎样的关系?
为什么pid已经可以标识唯一一个进程了,还需要端口号呢?
- a. 为了让系统和网络相互解耦。
- b. 需要客户每一次都能找到服务器进程 -- 服务器的唯一性不能做任何改变 -- IP+port 可以保证不变,IP+pid不能保证不变。
- c. 服务端的IP+port号 是不变的。
- d. 不是所有的进程都需要提供网络服务,但是所有的进程都需要pid。
另外,一个进程可以绑定多个端口号;但是一个端口号不能被多个进程绑定;
在OS系统内部维护了一张哈希表,把port和pid地址关联起来。
1.2.2.理解源端口号和目的端口号
传输层协议(TCP和UDP)的数据段中有两个端口号,分别叫做源端口号和目的端口号.就是在描述"数据是谁发的,要发给谁"。
1.3.认识TCP/UDP协议
1.3.1.TCP协议
此处我们先对TCP(Transmission Control Protocol传输控制协议)有一个直观的认识;后面我们再详细讨论TCP的一些细节问题.
- 传输层协议
- 有连接
- 可靠传输
- 面向字节流
1.3.2.UDP协议
此处我们也是对UDP(User Datagram Protocol用户数据报协议)有一个直观的认识;后面再详细讨论
- 传输层协议
- 无连接
- 不可靠传输
- 面向数据报
这里的可靠和不可靠是协议的特征无好坏贬义的意思。只是不同的协议表现出不同的特性。可靠是有成本的,可靠意味着复杂,不可靠意味着简单好用。
1.4.网络字节序
计算机在内存中存贮数据的时候分为大端和小端。
- 大端就是把高权值的数据放在低地址处。
- 小端就是把低权值的数据放在低地址处。(小小小)
而且每一个机器使用的机器字节序还可能不相同,在网络中发送数据的时候,是按照大端的顺序发送还是按照小端的顺序发送。接收者怎么知道这是大端还是小端。这就出问题了。
- 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;
- 接收主机把1
- 不管这台主机是大端机还是小端机,都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端,就需要先将数据转成大端;否则就忽略,直接发送即可;
网络字节序和主机字节序的转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
- 这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
- 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
- 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
- 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。
2.socket编程接口
(IP+port号)称为socket(套接字)。
2.1.sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket.然而,各种网络协议的地址格式并不相同.
- 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结构体指针做为参数;
struct sockaddr_in 的具体结构:
2.2.socket常见API
//创建socket文件描述符(TCP/UDP,客户端+服务器)
int socket(int domain, int type, int protocol);
//domain(域):选择使用本地通讯还是网络通讯。
1.AF_UNIX/AF_LOCAL(域间通讯);
2.AF_INET(使用ipv4协议);(AF_INET == PF_INET)
3.AF_INET6(使用ipv6协议);
4.其他不常用,不做介绍了。
//type:套接字提供服务的类型。
1.SOCK_STREAM(提供流式服务)(对应TCP)
2.SOCK_DGRAM(提供用户数据报套接字)(对应UDP)
3.其他不常用
//protocol:
1.具体协议的类型,但是一般默认写0 即可。因为一般我们在使用socket的时候前面两个参数写完之后默认使用的协议已经固定了,一般protocol这个参数设为0即可。
//返回值
1.返回一个文件描述符(fd)
2.如果失败返回-1,并且错误码被设定。
3.以后的操作会变为文件或者类文件操作。读(read),写(write),关闭(close)
//绑定端口号(TCP/UDP,服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);
//socket: socket返回的文件描述符。
//address: 绑定的 ip+port+协议家族,注意在填充struct sockaddr_in的时候应该为网络字节序(大端)。
//struct sockaddr_in 对象的长度。
//如果是虚拟机/独立真实的Linux环境,可以bind自己的公网IP。
//可以绑定自己的内网IP,但是作用不大,只能在局域网通讯。
//实际上一款网络服务器,不建议指明一个IP,一般都填充INADDR_ANY(0.0.0.0)。
//叫做任意地址绑定。
//按字节为单位向一块内存里面写零
void bzero(void* s, size_t n);//头文件<strings.h> / <cstrings>
//点分十进制 转化为 uint32_t网络字节序
in_addr_t inet_addr(const char *cp);
//注意这里输出in_addr_t == uint32_t 并且直接是网络字节序。
//udp读取数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
//flags==0:阻塞读取,有数据就读没数据就等待。
src_addr:发送方ip和port(输出型参数)
addrlen:发送方struct sockaddr结构体大小(输入输出型参数)
//网络中字节序uint32_t 转化为 点分十进制的字符串
char *inet_ntoa(struct in_addr in);
//udp发送数据,
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
sockfd:发送数据对应的文件描述符
buf+len 发送的数据
flags==0-:阻塞发送有数据就发,没数据就等。
dest_addr+addrlen : 目的IP和Port信息
//开始监听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);
2.3.点分十进制到init32_t之间的转化的原理
2.3.1. 12345 ---> “27.48.0.0”
int main()
{
uint32_t ip = 12345; //对应 57.48.0.0
struct _ip
{
unsigned char p1;
unsigned char p2;
unsigned char p3;
unsigned char p4;
};
std::string str_ip;
str_ip += std::to_string((int)((struct _ip *)&ip)->p1);
str_ip += '.';
str_ip += std::to_string((int)((struct _ip *)&ip)->p2);
str_ip += '.';
str_ip += std::to_string((int)((struct _ip *)&ip)->p3);
str_ip += '.';
str_ip += std::to_string((int)((struct _ip *)&ip)->p4);
std::cout << str_ip << std::endl;
}
2.3.2. “27.48.0.0” ---> 12345
int main()
{
struct _ip
{
unsigned char p1;
unsigned char p2;
unsigned char p3;
unsigned char p4;
};
std::string str_ip = "57.48.0.0";
int posc = 0;
auto pos = str_ip.find('.',0);
int p1 = atoi(str_ip.substr(posc, pos).c_str());
posc = pos;
pos = str_ip.find('.', posc + 1);
int p2 = atoi(str_ip.substr(posc+1, pos).c_str());
posc = pos;
pos = str_ip.find('.', posc + 1);
int p3 = atoi(str_ip.substr(posc+1, pos).c_str());
posc = pos;
pos = str_ip.find('.', posc + 1);
int p4 = atoi(str_ip.substr(posc+1, pos).c_str());
struct _ip tmp ;
tmp.p1 = p1;
tmp.p2 = p2;
tmp.p3 = p3;
tmp.p4 = p4;
std::cout<<*((uint32_t*)&tmp)<<std::endl;//12345
}
注意大小端不一样 可能对应的不一样。不建议自己转化。使用系统接口即可。
2.4.查看网络情况
2.4.1.netstat命令
参数:
- -a:所有的
- -u:udp协议对应的
- -p:显示进程信息
- -n:能显示数字的用数字替换。
注意:
- 127.0.0.1是本地环回地址,贯穿本机网络协议后在返回,用来测试。
- 云服务器是虚拟化的服务器,不能直接绑定自己的公网ip。
- 如果是虚拟机/独立真实的Linux环境,可以bind自己的公网IP。
- 可以绑定自己的内网IP,但是作用不大,只能在局域网通讯。
- 实际上一款网络服务器,不建议指明一个IP,一般都填充INADDR_ANY(0.0.0.0)。
2.5.udp协议实现网络字典
源码:
udp_server.hpp
#include <iostream>
#include <string>
#include <functional>
#include <cstring> //strerror
#include <cerrno> //errno
#include <cstdlib> //exit
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
namespace server
{
typedef std::function<void(int, std::string, uint16_t, std::string)> func_t;
enum
{
USAGE_ERROR = 1,
SOCKET_ERROR,
BIND_ERROR,
OPEN_ERROE,
CATLINE_ERROR
};
class udpServer
{
// const static std::string defaultIp ;
static const std::string defaultIp;
public:
udpServer(func_t func, const uint16_t &port, const std::string ip = defaultIp)
: _port(port), _ip(ip), _sockfd(-1), _func(func)
{
}
~udpServer()
{
}
void initServer()
{
// 创建套接字。
_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (_sockfd < 0)
{ // 创建失败
std::cerr << "socket error!! " << errno << ": " << strerror(errno) << std::endl;
exit(SOCKET_ERROR);
}
// 绑定ip+port
struct sockaddr_in local; // sockaddr_in 使用的时候要包含头文件 <netinet/in.h> 或者 <arpa/inet.h>
bzero(&local, sizeof(local));
// 填入 协议家族,端口号,ip地址(uint32_t类型的)
local.sin_family = AF_INET; // 指定协议家族
local.sin_port = htons(_port); // 指定端口号 //注意主机字节序转化为网络字节序
// local.sin_addr.s_addr =inet_addr(_ip.c_str());
local.sin_addr.s_addr = INADDR_ANY; // 任意地址绑定 服务器的真实写法
// 指定ip(uint32_t) //注意1.点分十进制转化为uint32_t; 2.主机字节序转化为网络字节序。
int ret = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
// 注意强制类型转化,(struct sockaddr*)
if (ret == -1)
{
std::cerr << "bind error!! " << errno << ": " << strerror(errno) << std::endl;
exit(BIND_ERROR);
}
// 初始化完成
}
void startServer()
{
// 服务器的本质就是一个死循环。
// 死循环的代码也叫常驻内存进程
// 只有死循环的进程,不退出的进程,才会在乎内存泄漏。
char buf[1024];
for (;;)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer); // 这里不能省去。
int s = recvfrom(_sockfd, buf, sizeof buf, 0, (struct sockaddr *)&peer, &len);
if (s > 0)
{
// 读取成功,
buf[s] = 0;
// 数据再buf中,客户端的信息在peer中
// 将客户端信息转化出来
std::string clinetip = inet_ntoa(peer.sin_addr);
uint16_t clientport = ntohs(peer.sin_port);
std::string message = buf;
_func(_sockfd, message, clientport, clinetip);
// 这里设置了一个对调函数,实现通讯和业务逻辑解耦的操作。
}
}
}
private:
uint16_t _port; // server——端口号
std::string _ip; // server——ip
int _sockfd; // socket的返回值的文件描述符。
func_t _func; // 设置回调函数
};
const std::string udpServer::defaultIp = "0.0.0.0";
// 静态成员一定要在类外面进行初始化。
} // server end
udp_server.cc
#include "udp_server.hpp"
#include <unordered_map>
#include <fstream>
#include <memory>
#include <signal.h>
using namespace server;
std::unordered_map<std::string, std::string> dict; // 字典
std::string dicttxt = "./dict.txt"; // 配置文件
std::string sep = ":"; // 分隔符
// 未来不同的udp服务器其实就是在这里不一样。业务逻辑的处理不一样。
void serverfunc(int sockfd, std::string message, uint16_t clientport, std::string clinetip)
{
// 打印接受的数据
std::cout << clinetip << "[" << clientport << "]#" << message << std::endl;
struct sockaddr_in client_addr;
bzero(&client_addr,sizeof(client_addr));
client_addr.sin_family = AF_INET;
client_addr.sin_port = htons(clientport);
client_addr.sin_addr.s_addr = inet_addr(clinetip.c_str());
std::string retstr;
if (dict.end() == dict.find(message))
{
retstr = "没找到!!";
}
else
{
retstr = dict[message];
}
sendto(sockfd, retstr.c_str(), retstr.size(), 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
std::cout << "发送数据: " << retstr << std::endl;
}
static bool catline(const std::string &line, std::string *key, std::string *value)
{
auto pos = line.find(sep);
if (pos == std::string::npos)
{
return false;
}
*key = line.substr(0, pos);
*value = line.substr(pos + sep.size());
return true;
}
void dictinit()
{
std::ifstream in(dicttxt, std::ios::binary);
if (!in.is_open())
{
// 打开失败
std::cerr << "open file" << dicttxt << "error!!" << std::endl;
exit(OPEN_ERROE);
}
std::string line, key, value;
while (getline(in, line))
{
if (catline(line, &key, &value))
{
dict.insert(make_pair(key, value));
}
else
{
std::cout << "catline error" << std::endl;
exit(CATLINE_ERROR);
}
}
in.close();
}
// test
void printdict()
{
for (auto e : dict)
{
std::cout << e.first << "#" << e.second << std::endl;
}
}
// 使用手册
void usage(char *proc)
{
std::cout << "Usage: \n\t" << proc << " local_port\n\n";
}
void handler(int sig)
{
//支持热加载。
dictinit();
std::cout<<"字典更新完成"<<std::endl;
}
// 未来将来吧 ,Ip和Port 传进来,我们需要用到,命令行参数。
// 使用 :"./server local_ip local_port"
int main(int argc, char *argv[])
{
signal(3, handler);
if (argc != 2)
{
usage(argv[0]);
exit(USAGE_ERROR);
}
// //port
uint16_t port = atoi(argv[1]);
// uint16_t port = 10002;
dictinit();
// printdict();
std::unique_ptr<udpServer> us(new udpServer(serverfunc, port)); // 不用传入ip,使用0.0.0.0
us->initServer();
us->startServer();
return 0;
}
udp_client.hpp
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <cstring>
#include <stdlib.h>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
namespace client
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR = 2,
BIND_ERR = 3
};
class UdpClient
{
public:
UdpClient(uint16_t serverport, std::string serverip)
: _serverport(serverport), _serverip(serverip)
{
}
void initclient()
{
int ret = socket(AF_INET, SOCK_DGRAM, 0);
if (ret == -1)
{
std::cout << "socket error: " << errno << ":" << strerror(errno) << std::endl;
exit(SOCKET_ERR); // 退出码是自己设置的。
}
_sockfd = ret;
std::cout << "socket success: "
<< " : " << _sockfd << std::endl;
// 客户端不需要显示bind,//OS会自己绑定对应 的IP和port
// 所有初始化任务完成。
}
void startclient()
{
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str()); // 1.string-》uint_t; 2.主机字节序-》网络字节序
server.sin_port = htons(_serverport); // 主机转网络字节序。
char buf[1024];
while (true)
{
std::string message;
std::cout << "Please Enter# ";
std::cin >> message;
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
std::cout << "发送数据:" << message << std::endl;
struct sockaddr_in server;
socklen_t len = sizeof(server);
int s = recvfrom(_sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&server, &len);
if (s > 0)
{
buf[s] = 0;
std::cout << "接受数据:" << buf << std::endl;
}
}
}
~UdpClient()
{
}
private:
std::string _serverip;
uint16_t _serverport;
int _sockfd;
};
}
udp_client.cc
#include "udp_client.hpp"
#include <memory>
using namespace client;
// 使用手册
void usage(char *proc)
{
std::cout << "Usage: \n\t" << proc << " server_ip server_port\n\n";
// 客户端在发送消息的时候,使用的是服务端的公网IP
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
usage(argv[0]);
exit(USAGE_ERR);
}
// uint16_t serverport = 10002;
uint16_t serverport = atoi(argv[2]);
std::string serverip = argv[1];
std::unique_ptr<UdpClient> uc(new UdpClient(serverport, serverip)); // 不用传入ip,使用0.0.0.0
uc->initclient();
uc->startclient();
return 0;
}
2.6.远端的shell解释器
使用的接口:
#include <stdio.h>
FILE *popen(const char *command, const char *type);//pipe+fork+exec
//执行传入的命令。执行结果以文件方式返回。
//command:未来的命令行字符串
//type:文件的打开方式 “r” “w” ---
int pclose(FILE *stream);
//读取执行结果之后需要关闭返回的文件描述符
源码:
// 未来不同的udp服务器其实就是在这里不一样。业务逻辑的处理不一样。
//上述代码修改这里就可以修改整个服务器处理逻辑。
void serverfunc(int sockfd, std::string cmd, uint16_t clientport, std::string clinetip)
{
// 打印接受的数据
std::cout << clinetip << "[" << clientport << "]#" << cmd << std::endl;
FILE* fp = popen(cmd.c_str(), "r");
std::string retstr;
char line[1024];
while(fgets(line, sizeof(line)-1, fp))
{
retstr += line;
}
struct sockaddr_in clinet_addr;
bzero(&clinet_addr, sizeof(clinet_addr));
clinet_addr.sin_family= AF_INET;
clinet_addr.sin_port = htons(clientport);
clinet_addr.sin_addr.s_addr = inet_addr(clinetip.c_str());
sendto(sockfd, retstr.c_str(), retstr.size(),0 ,(struct sockaddr* )&clinet_addr, sizeof(clinet_addr));
pclose(fp);
}
这就是一个原理版本的 shell(远端命令行解释器),我们写的只能说明原理,有些命令是不能执行的。
2.7.udp——实现网络聊天室
udp_sercer.cc //替换对应的处理逻辑
// 未来不同的udp服务器其实就是在这里不一样。业务逻辑的处理不一样。
//替换上面的serverfunc函数即可实现不同的服务器处理替换。
void serverfunc(int sockfd, std::string message, uint16_t clientport, std::string clinetip)
{
// 打印接受的数据
// std::cout << clinetip << "[" << clientport << "]#" << cmd << std::endl;
User user(clinetip, clientport);
if (message == "online")
{
users.userAdd(user);
}
if (message == "delete")
{
users.userDelet(user);
}
if (users.userFind(user.getname()))
{
// 群发数据
std::string retstr;
retstr += user.getname();
retstr += " ";
retstr += std::to_string(time(NULL));
retstr += " ";
retstr += "#";
retstr += message;
users.allreply(sockfd, retstr);
}
else
{
// 用户没有登录 //单发数据
std::string retstr;
retstr += "你还没有登录,请先输入“online” 登录!!!";
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(user._port);
peer.sin_addr.s_addr = inet_addr(user._ip.c_str());
socklen_t len = sizeof(peer);
sendto(sockfd, retstr.c_str(), retstr.size(), 0, (struct sockaddr *)&peer, len);
}
}
usr_manege.hpp //用户结构体的构建和组织。
#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
class User
{
public:
User(std::string ip, uint16_t port)
: _ip(ip), _port(port)
{
std::string str = std::to_string(_port);
_username += _ip;
_username += "[";
_username += str;
_username += "]";
}
std::string getname()
{
return _username;
}
std::string _ip;
uint16_t _port;
private:
std::string _username;
};
class Usermanager
{
public:
Usermanager()
{
}
~Usermanager()
{
}
void userAdd(User &val)
{
_map.insert(std::make_pair(val.getname(), val));
}
void userDelet(User &val)
{
_map.erase(val.getname());
}
std::unordered_map<std::string, User> getmap()
{
return _map;
}
bool userFind(const std::string &kay)
{
return _map.find(kay) != _map.end();
}
void allreply(int sockfd, std::string& message)
{
for (auto e : _map)
{
User user = e.second;
struct sockaddr_in peer;
peer.sin_family = AF_INET;
peer.sin_port = htons(user._port);
peer.sin_addr.s_addr = inet_addr(user._ip.c_str());
socklen_t len = sizeof(peer);
sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&peer, len);
}
}
private:
std::unordered_map<std::string, User> _map;
};
udp_clinet.hpp //
#pragma once
#include <iostream>
#include <string>
#include <cassert>
#include <cstring>
#include <stdlib.h>
#include <cerrno>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
namespace client
{
enum
{
USAGE_ERR = 1,
SOCKET_ERR = 2,
BIND_ERR = 3
};
class UdpClient
{
public:
UdpClient(uint16_t serverport, std::string serverip)
: _serverport(serverport), _serverip(serverip)
{
}
void initclient()
{
int ret = socket(AF_INET, SOCK_DGRAM, 0);
if (ret == -1)
{
std::cout << "socket error: " << errno << ":" << strerror(errno) << std::endl;
exit(SOCKET_ERR); // 退出码是自己设置的。
}
_sockfd = ret;
std::cout << "socket success: "
<< " : " << _sockfd << std::endl;
// 客户端不需要显示bind,//OS会自己绑定对应 的IP和port
// 所有初始化任务完成。
}
static void *readfunc(void *arg)
{
int sockfd = *(static_cast<int *>(arg));
char buf[1024];
struct sockaddr_in server;
socklen_t len = sizeof(server);
while (true)
{
int s = recvfrom(sockfd, buf, sizeof(buf) - 1, 0, (struct sockaddr *)&server, &len);
if (s > 0)
{
buf[s] = 0;
std::cout << buf << std::endl;
}
}
}
void startclient()
{
pthread_create(&_readpid, nullptr, readfunc, &_sockfd);
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(_serverip.c_str()); // 1.string-》uint_t; 2.主机字节序-》网络字节序
server.sin_port = htons(_serverport); // 主机转网络字节序。
char buf[1024];
while (true)
{
std::string message;
std::cerr << "Please Enter# ";
getline(std::cin, message);
sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));
}
}
~UdpClient()
{
}
private:
std::string _serverip;
uint16_t _serverport;
int _sockfd;
pthread_t _readpid;
};
}
不同的客户端对应的窗口,所使用的ip+port不一样。
2.8.Windows版本的的网络套接字
对上面的网络字典代码写一个windows客户端:
win_udp_clinet.cpp
//注意此代码要拷贝到windows下编译即可
#include<iostream>
#include<string>
//首先win打Linux下的sock接口都是一样的。无差别。
//只有四点不同
// 1. win需要包含头文件 <winSock2.h>
// 2. 引入库 #pragma comment(lib, "ws2_32.lib")
// 3. 开始初始化winsock和启动winsock; 最后关闭winsock
// 4. SOCKET 相当于 int 也就是Linux中打开sock 返回的文件描述符
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
std::string ip = "82.157.245.253";
uint16_t port = 8080;
int main()
{
//初始化winsock
WSAData wsd;
//启动winsock
if (WSAStartup(MAKEWORD(2, 2), &wsd))
{
std::cout << "WSAStartup error!!" << std::endl;
return 0;
}
else
{
std::cout << "WSAStartup success!!" << std::endl;
}
SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);//IPPROTO_UDP //默认是0即可,也可也写上对应的宏。
if (sock == SOCKET_ERROR)//这里SOCKET_ERROR的值其实就是-1 ,和我们以前LInux的套接字一样
{
std::cout << "socket error !!" << WSAGetLastError() << std::endl;
return 1;
}
else
{
std::cout << "socket success!!" << std::endl;
}
struct sockaddr_in server;
memset(&server, 0, sizeof(server));
server.sin_family = AF_INET;
//server.sin_addr.s_addr = inet_addr(ip.c_str()); // 1.string-》uint_t; 2.主机字节序-》网络字节序
server.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
// 1.string-》uint_t; 2.主机字节序-》网络字节序
server.sin_port = htons(port); // 主机转网络字节序。
char buf[1024];
while (true)
{
std::string message;
std::cout << "Please Enter# ";
std::getline(std::cin, message);
sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr*)&server, sizeof(server));
std::cout << "发送数据:" << message << std::endl;
struct sockaddr_in server;
int len = sizeof(server);
int s = recvfrom(sock, buf, sizeof(buf) - 1, 0, (struct sockaddr*)&server, &len);
if (s > 0)
{
buf[s] = 0;
std::cout << "接受数据:" << buf << std::endl;
}
}
//关闭套接字
closesocket(sock);
//关闭winsock
WSACleanup();
return 0;
}