基于UDP的P2P聊天工具——0.1

基于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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值