基于UDP的P2P聊天工具 0.1
简介:
1)这是一个windows的P2P聊天工具;
2)它支持局域网内1对1发送文字消息;
3)它的界面是dos控制台界面;
相关内容:
1)入门级UDP套接字编程;
2)UDP的connect函数;
3)入门级windows多线程;
一、入门级UDP套接字编程
作为套接字,它包含两个要素:协议和地址。所以,在网络编程中,一般的套路就是,根据协议创建套接字,然后绑定地址。
1)创建套接字
m_sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
2)指明地址信息,并绑定地址
sockaddr_in servaddr = { 0 };
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
bind(m_sockfd, (const sockaddr*)&servaddr, sizeof(servaddr));
3)UDP消息的收发
UDP的消息收发通常使用recvfrom和sendto。但是,由于我们这里使用另一种方式,所以这里对这两个函数不做细说。
二、UDP的connect函数和消息收发
我们知道UDP是一种无连接的协议。但是,对于UDP套接字,我们仍然可以调用connect函数,只是这时的connect与TCP中的connect是不同的:
”在TCP中,connect引发三次握手的过程;而在UDP中,它只是检查是否存在立即克制的错误(如一个显然不可达的目的地),然后记录对端的IP地址和端口号“。这句话出自《UNIX网络编程》卷1第三版的8.11节。
给UDP套接字调用connect有三点好处:
1)由于对端的地址已经被记录,我们可以使用recv和send进行消息收发。最直观的变化就是,recv/send所需要的入参,比recvfrom/sendto少了两个。
2)当对端不存在时,可以很快知道。
3)性能上获得提升。不过,由于这里只是简单的1对1字符消息收发,所以可能感觉不到明显差异。
// 这里的servaddr记录对端的地址
sockaddr_in servaddr = { 0 };
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(ip);
servaddr.sin_port = htons(port);
connect(m_sockfd, (const sockaddr*)&servaddr, sizeof(servaddr));
send(m_sockfd, buf, len, 0);
recv(m_sockfd, buf, len, 0);
三、入门级windows多线程
在linux中,标准输入和套接字同样可以作为文件描述符进行处理。所以它可以使用select等,很方便地进行多路复用。
但是,在windows中,标准输入跟套接字没法当作一回事进行处理。所以,为了简单处理,这里使用多线程的方式进行处理。
在主线程中,使用recv进行阻塞式等待接收来自对端的消息;在子线程中,接收标准输入的消息,然后发送给对端。
windows中开线程的方式,可以像这样:
1)首先,定义一个线程函数;2)然后,创建线程。
DWORD WINAPI ThreadFunc(LPVOID pParam)
{
cout << "hello world" << endl;
}
HANDLE handle = CreateThread(NULL, 0, ThreadFunc, this, 0, NULL);
四、代码
包含Talker.h,Talker.cpp,main.cpp,start.bat;
main.cpp
#include <iostream>
#include "Talker.h"
using namespace std;
int main(int argc, char** argv)
{
if (argc < 4)
{
cout << "<usage>: Talker.exe myPort peerIp peerPort" << endl;
}
else
{
Talker talker(atoi(argv[1]), argv[2], atoi(argv[3]));
talker.Start();
}
system("pause");
return 0;
}
start.bat (作为使用示例;ps. 注意路径、IP、端口)
@echo off
if exist ./Talker.exe (
start Talker.exe 33333 127.0.0.1 44444
start Talker.exe 44444 127.0.0.1 33333
) else (
echo "./Talker.exe does not exist"
pause
)
Talker.h
#pragma once
#include <windows.h>
#pragma comment(lib, "ws2_32")
class Talker
{
public:
// 初始化WSADATA,绑定本地端口,建立UDP“连接”;
Talker(unsigned short myport, const char* peerip, unsigned short peerPort);
// 清理WSADATA和套接字;
~Talker();
// 创建线程,获取标准输入,发送给对端;
// 接收来自对端的消息,并打印;
void Start();
int Send(const char* buf, int len);
int Recv(char * buf, int len);
private:
SOCKET m_sockfd;
// 初始化WSADATA;
void init();
// 套接字绑定本地端口;
void OpenPort(unsigned short port);
// 建立UDP“连接”;
void AimAt(const char* ip, unsigned short port);
};
Talker.cpp
#include <iostream>
#include "Talker.h"
using namespace std;
// 获取标准输入,再发送给对端;它以线程函数的形式被调用;
DWORD WINAPI ThreadFunc(LPVOID pParam)
{
while (true)
{
char buf[MAXBYTE] = { 0 };
cin.getline(buf, MAXBYTE);
((Talker*)pParam)->Send(buf, strlen(buf) + 1);
}
return 0;
}
// 初始化WSADATA,绑定本地端口,建立UDP“连接”;
Talker::Talker(unsigned short myport, const char* peerip, unsigned short peerPort)
{
init();
OpenPort(myport);
AimAt(peerip, peerPort);
}
// 清理WSADATA和套接字;
Talker::~Talker()
{
closesocket(m_sockfd);
WSACleanup();
}
// 初始化WSADATA;
void Talker::init()
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
{
cerr << "Could not load winsock" << endl;
exit(1);
}
}
// 套接字绑定本地端口;
void Talker::OpenPort(unsigned short port)
{
sockaddr_in servaddr = { 0 };
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
if ((m_sockfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP)) == INVALID_SOCKET)
{
cerr << "socket fail" << endl;
exit(WSAGetLastError());
}
if (bind(m_sockfd, (const sockaddr*)&servaddr, sizeof(servaddr)) != 0)
{
cerr << "bind fail" << endl;
exit(WSAGetLastError());
}
}
// 建立UDP“连接”;
void Talker::AimAt(const char* ip, unsigned short port)
{
sockaddr_in servaddr = { 0 };
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(ip);
servaddr.sin_port = htons(port);
if (connect(m_sockfd, (const sockaddr*)&servaddr, sizeof(servaddr)) < 0)
{
cerr << "connect fail" << endl;
exit(WSAGetLastError());
}
}
// 创建线程,获取标准输入,发送给对端;
// 接收来自对端的消息,并打印;
void Talker::Start()
{
cout << "talking ..." << endl;
HANDLE handle = CreateThread(NULL, 0, ThreadFunc, this, 0, NULL);
while (true)
{
char buf[MAXBYTE] = { 0 };
if (Recv(buf, sizeof(buf)) >= 0)
cout << "from peer: " << buf << endl;
}
}
int Talker::Send(const char* buf, int len)
{
if (send(m_sockfd, buf, len, 0) < 0)
{
cerr << "send fail" << endl;
exit(WSAGetLastError());
}
return 0;
}
int Talker::Recv(char * buf, int len)
{
if (recv(m_sockfd, buf, len, 0) < 0)
{
cerr << "recv fail" << endl;
exit(WSAGetLastError());
}
return 0;
}
五、说明
这个代码是可以运行的,而且在运行start.bat后生成的两个黑框也确实是可以相互发消息的。不过它其实还是有点问题的。这在另一篇里会进行介绍——基于UDP的P2P聊天工具 0.2