目录
1.预备知识
1.1.理解源IP地址和目的IP地址
在IP数据包头部中,有两个IP地址,分别叫做源IP地址,和目的IP地址。确定的在路由期间的方向性。为转发的每一个阶段提供方向。
1.2.认识端口号
思考:我们光有IP地址就可以完成通信了嘛?想象一下发qq消息的例子,有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出,这个数据要给哪个程序进行解析.你怎么知道这个数据段给qq还是微信。
端口号(port)是传输层协议的内容.
网络通讯的本质就进程间通讯。也是进程到进程之间的通讯。两个进程都要看到同一份资源--网络。通讯就是在做IO(收数据或者发送数据)。
1.2.1.理解"端口号"和"进程ID"
我们之前在学习系统编程的时候,学习了pid表示唯一一个进程;此处我们的端口号也是唯一表示一个进程.那么这两者之间是怎样的关系?
为什么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.网络字节序
计算机在内存中存贮数据的时候分为大端和小端。
而且每一个机器使用的机器字节序还可能不相同,在网络中发送数据的时候,是按照大端的顺序发送还是按照小端的顺序发送。接收者怎么知道这是大端还是小端。这就出问题了。
网络字节序和主机字节序的转换
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
2.socket编程接口
(IP+port号)称为socket(套接字)。
2.1.sockaddr结构
socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及后面要讲的UNIX Domain Socket.然而,各种网络协议的地址格式并不相同.
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命令
参数:
注意:
2.5.udp协议实现网络字典
源码:
#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
#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;
}
#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;
};
}
#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服务器其实就是在这里不一样。业务逻辑的处理不一样。
//替换上面的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);
}
}
#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;
};
#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客户端:
//注意此代码要拷贝到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;
}