UDP协议做的windows窗口聊天程序---此处是单向的"同步"多线程对话框通信

初识用UDP协议做的windows窗口聊天程序---此处是单向的"同步"多线程对话框通信.下部分讲双向通信
2010年06月11日 星期五 上午 11:38

问题1:进程与线程基础知识?
1.进程从来不执行任何东西,它只是线程的容器(注:每个进程必须有一线程.操作系统会自动创建)
2.线程拥有自己的私有地址空间(例:进程1与2都有一个地址0x1234,那么同时访问时,1只能访问1中的,2也只能访问2中的,互不干扰)
3.线程同时运行的原因?操作系统为每个运行线程安排"CPU时间片",一人用一段时间,轮流(注:轮流时间很短,可以用time函数推迟时间)
问题2:编写一个最基本的多线程,为什么代码一样,却出现乱码?




-----出现这种原因的根本问题是,以前使用的是单核CPU,现在使用的是多核,所以造成并发运行,混乱输出
解决办法---就是在输出之前给足时间片sleep(其中随输出大小而定,越大时间越长),这是之前的解决办法(不要用),最好办法设置线程保护
问题3:在问题2上增加while(i++<100)函数,出现多核CPU并发运行?
-----问题3同问题2一样,都是多核引起,CPU并发,造成乱码.怎样解决?只有实现线程保护,参看问题4线程保护之一:互斥对象
问题4:在多核环境下编写一个多线程售票?



------解决:线程保护一定要在线程还没创建之前就开启,否则一量创建就运行,保护就迟了.
------单与多核区别之处?单核在线程创建之后(注:并没规定必须要后,只是书上介绍,此处为纠正错误才提出,最好在前),多核在创建之前保护
------详见代码(正常运行是图上结果,此处改动为线程2专门卖票,是为了说明一个问题,若想正常运行,只需复制线程1代码即可,cout<<"线程1(这个1要改为2,好区分))

#include<windows.h>
#include<iostream>
using namespace std;

int ticket=100;
DWORD WINAPI fun1_thread(LPVOID lpParameter );
DWORD WINAPI fun2_thread(LPVOID lpParameter );
HANDLE hMutex;
void main()
{hMutex=CreateMutex(0,0,0);//注:线程保护一定要在线程还没创建之前就开启,否则一量创建就运行,保护就迟了
HANDLE hThread1=CreateThread(0,0,fun1_thread,0,0,0);HANDLE hThread2=CreateThread(0,0,fun2_thread,0,0,0);
CloseHandle(hThread1);CloseHandle(hThread2);

Sleep(4000);//时间必须要延迟的,否则很可能处理不完.当然时间长短随传输的大小而定
}

DWORD WINAPI fun1_thread(LPVOID lpParameter )
{
while(1)
{
   WaitForSingleObject(hMutex, INFINITE);
   if(ticket>0)
    cout<<"线程1卖了第"<<ticket--<<endl;
   else
    break;
//   Sleep(1000);//注:总共时间为4秒,现在用了1秒.运行一次,就要等1秒.下面的根本没时间了.所以谨慎添加
   ReleaseMutex(hMutex);
}return 0;
}

DWORD WINAPI fun2_thread(LPVOID lpParameter )
{WaitForSingleObject(hMutex, INFINITE);//注:此处虽然可以放在while外,但结果是只有此线程卖票.线程2根本不响应
while(1)//运行结果是线程1,运行一次后,剩余99张票都是
{

   if(ticket>0)
    cout<<"线程2卖了第"<<ticket--<<endl;
   else
    break;
  
}ReleaseMutex(hMutex);return 0;
}

问题5:拥有互斥对象的线程,一旦终止,而没有调用ReleaseMutex,会发生什么情况?



-----因为已经设置线程保护,所以退出时会遵守保护的规则.自动ID为0,并将计数器归0,这样就释放了自己的保护权
-----举例:2个没有ReleaseMutex的线程.一旦线程1,运行完,就运行线程2
-----详细代码:复制过去就能用

#include<windows.h>
#include<iostream>
using namespace std;

int ticket=100;
DWORD WINAPI fun1_thread(LPVOID lpParameter );
DWORD WINAPI fun2_thread(LPVOID lpParameter );
HANDLE hMutex;
void main()
{hMutex=CreateMutex(0,0,0);//注:线程保护一定要在线程还没创建之前就开启,否则一量创建就运行,保护就迟了
HANDLE hThread1=CreateThread(0,0,fun1_thread,0,0,0);HANDLE hThread2=CreateThread(0,0,fun2_thread,0,0,0);
CloseHandle(hThread1);CloseHandle(hThread2);

Sleep(4000);//时间必须要延迟的,否则很可能处理不完.当然时间长短随传输的大小而定
}

DWORD WINAPI fun1_thread(LPVOID lpParameter )
{

WaitForSingleObject(hMutex, INFINITE);//这2行单独的代码表示是
cout<<"线程1卖了第"<<ticket--<<endl;
return 0;
}

DWORD WINAPI fun2_thread(LPVOID lpParameter )
{
WaitForSingleObject(hMutex, INFINITE);//这2行单独的代码表示是
cout<<"线程2卖了第"<<ticket--<<endl;
return 0;
}

问题6:怎样保证程序同一时间只能运行一个?



-----默认情况下:编译出的.exe都是可以同时打开N个,但是有些软件有这个需求,需要只有1个打开,怎么办呢,用互斥对象的返回值就能判断
-----详细代码

#include<windows.h>
#include<iostream>
using namespace std;

DWORD WINAPI fun1_thread(LPVOID lpParameter );
DWORD WINAPI fun2_thread(LPVOID lpParameter );
HANDLE hMutex;
void main()
{

hMutex=CreateMutex(0,0,"1号");//此段最重要的地方:一定要给互斥对象取名.否则不能获得重复错误.
if(hMutex)
{
if(GetLastError()==ERROR_ALREADY_EXISTS)//注:ERROR_INVALID_HANDLE错误.因为它匹配的是参数3的1号,而此匹配的是整个
{cout<<"只能同时运行一个实例"<<endl;return;}
}
HANDLE hThread1=CreateThread(0,0,fun1_thread,0,0,0);HANDLE hThread2=CreateThread(0,0,fun2_thread,0,0,0);
CloseHandle(hThread1);CloseHandle(hThread2);

Sleep(1000);
}

DWORD WINAPI fun1_thread(LPVOID lpParameter )
{
WaitForSingleObject(hMutex, INFINITE);
cout<<"线程1在运行"<<endl;
return 0;
}

DWORD WINAPI fun2_thread(LPVOID lpParameter )
{
WaitForSingleObject(hMutex, INFINITE);
cout<<"线程2在运行"<<endl;
return 0;
}

备注1:启示---此处涉及到的技术是"给互斥对象取名字",也就是说以后的多线程编程中,可以使用不同的互斥对象运行,而互不冲突-----------也就是说线程中,管理秩序非常规范,大胆放心地去编写多线程吧.

*************************以下是本章重点:实战---编一个对话框通信软件(采用多线程)*****************************
问题7:首先回答第1个问题,为什么要用多线程?
-----如果想停留在DOS时代的黑屏窗口.就不必学多线程.如果哪天想自己编一个QQ,那么多线程是必须的.因为窗口除了监听,还要做别的
问题8:用AfxSocketInit函数加载套接字库,本博客<<winsock2.h与afxsock.h发生冲突,解决办法>>文章已详细介绍使用方法与兼容处理方法
#####################################



------------------为确保正确,所以亲自做出了那个程序,下面就是讲解步骤.注:备注1(代码区)与第1步(讲解区)同步------------------
前提:创建对话框应用程序,界面随意
第1步:初始化线程------->AfxSocketInit(至于头文件与放在哪,MSDN已说的很详细)
第2步:创建与绑定套接字->socket与bind--->放在哪?放在OnInitDialog()函数中(考虑到代码全放在一起不方便看,所以需建一个函数放)
------>用BOOL类型(为什么要用BOOL?因为出了问题,可以通过返回值判断提示.而void虽说可用,但出错了,却没有判断提示,不用)
第3步:创建线程(见备注3原因)--->CreateThread(注:记的用完后,用CloseHandle关闭线程)
第4步:等待消息--->recvfrom--->放在哪?放在子线程中,避开了阻塞--->记的收到的消息放到::PostMessage传递给别人
第5步:发送消息--->sendto--->完成了.
提示一下:此处最难的一点,就是防止阻塞,线程就避开了,所以重点是添加并处理好线程,第2个功臣就是::PostMessage传送消息
----------------------------
备注1:
BOOL CMy3App::InitInstance()
{ AfxSocketInit();
---
#include <afxsock.h>        //支持初始化winsock的头文件,放在StdAfx.h头文件中
---
备注2:
InitSock();这个放在OnInitDialog()函数中
m_socket在类中定义为私有,因为套接字是公开使用的,用私有,也就是封装性会安全些
BOOL CMy3Dlg::InitSock()
{
第2步:创建socket//
m_socket=socket(AF_INET,SOCK_DGRAM,0);
第3步:创建bind
SOCKADDR_IN sbind;
sbind.sin_family=AF_INET;
sbind.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
sbind.sin_port=htons(6000);
bind(m_socket,(SOCKADDR*)&sbind,sizeof(SOCKADDR));

return TRUE;//创建完后就返回了.注:返回1,继续运行
}
备注3:
1.为什么要创建线程?因为accept是一个死循环,如果你把它放在主线程中,就会造成对话框阻塞.解决办法是放在线程中,因为线程中有个时间

片,主与线程可以同时运行,又不发生阻塞.2全齐美
2.是不是很难创建线程?不是,别看教材上讲的乱七八糟,其实很有秩序的.你这样做:先在OnInitDialog函数中创建一个线程,然后看缺什么就写

什么.
3.The thread object remains in the system until the thread has terminated and all handles to it have been closed through a

call to CloseHandle. 翻译:1.只要线程不关,线程就一直运行 2.所有用完后的句柄要通过CloseHandle收回(注:句柄收回不代表关闭线程)

------下面就是部分代码,有很多是可以直接复制过去直接用的.比如获取IP,获取端口,直接复制过去就能用-----

InitSock();
StructThread *thread_3=new StructThread;thread_3->socket=m_socket;thread_3->hwnd=m_hWnd;
HANDLE hThread=CreateThread(0,0,fun_thread,(LPVOID)thread_3,0,0);CloseHandle(hThread);

BOOL CMy3Dlg::InitSock()
{
第2步:创建socket//
m_socket=socket(AF_INET,SOCK_DGRAM,0);
第3步:创建bind
SOCKADDR_IN sbind;
sbind.sin_family=AF_INET;
sbind.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
sbind.sin_port=htons(6000);
bind(m_socket,(SOCKADDR*)&sbind,sizeof(SOCKADDR));

return TRUE;//创建完后就返回了.注:返回1,继续运行

}
DWORD WINAPI CMy3Dlg::fun_thread( LPVOID lpParameter )
{
第4步:创建recvfrom
char c1[50];CString str_recv,str_send;SOCKADDR_IN srecv;int len=sizeof(SOCKADDR);
SOCKET s1=((StructThread*)lpParameter)->socket;HWND h1=((StructThread*)lpParameter)->hwnd;//注:为什么打括号
delete lpParameter;
while(1)
{
   recvfrom(s1,c1,50,0,(SOCKADDR*)&srecv,&len);
   ::PostMessage(h1,WM_RECV,0,(LPARAM)c1);
}
return 0;//虽然说是返回0,结束,但是此前有个while(1)死循环,其实此段仅是做样子,按格式写的好看的.必要的格式
}
void CMy3Dlg::OnS(WPARAM wParam,LPARAM lParam)
{
CString str_recv=(char*)lParam;
SetDlgItemText(IDE_RECV,str_recv);
}

void CMy3Dlg::OnSend()
{
char serverIP[16]; CString str_cip;GetDlgItemText(IDE_S_IP,str_cip);strncpy(serverIP,LPCTSTR(str_cip),16);
CString str_cport;GetDlgItemText(IDE_S_PORT,str_cport);UINT m_cport;m_cport=UINT(atoi(LPCSTR(str_cport)));

SOCKADDR_IN ssend;
ssend.sin_family=AF_INET;
ssend.sin_addr.S_un.S_addr=inet_addr(serverIP);
ssend.sin_port=htons(m_cport);
    CString str_recv,str_send;int len=sizeof(SOCKADDR);

GetDlgItemText(IDE_SEND,str_send);
sendto(m_socket,str_send,50,0,(SOCKADDR*)&ssend,sizeof(SOCKADDR));
SetDlgItemText(IDE_SEND,"");
}

总结:

第1点:另外本代码是经过封装处理的.很多直接可以复制过去,直接就能用.典型的例子,就是获取IP,获取端口,直接复制过去就能用.

第2点:本章省去枝叶(即错误提示),保存了精华部分,目的是让主题明确.当然在你真正开发时,务必加上这些枝叶,因为它可以为你判断错误,以后维护起来会更方便,而且也不会因错误发生,而无法停止,导致更严重事故等等,切记!

第3点:本章与DOS下完全一样.只是多了一个多线程处理.虽然书上写的乱七八糟,那是误导你.你只有一眼抓住主题,也就是CreateThread,其它部分不攻而破了.因为所有函数就是围绕它来.

当你第1次写这个程序时,或许需要好几天才能理解透.但是当你第2次写时,其实10分钟就能写完

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于UDP协议的简单聊天程序的设计与实现的代码示例: 服务端代码: ```python import socket # 创建UDP套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定服务端IP地址和端口号 server_address = ('127.0.0.1', 8888) server_socket.bind(server_address) while True: # 接收客户端发送的数据 data, client_address = server_socket.recvfrom(1024) print('Received message from {}: {}'.format(client_address, data)) # 发送响应数据给客户端 message = input('Enter message to send to {}: '.format(client_address)) server_socket.sendto(message.encode('utf-8'), client_address) ``` 客户端代码: ```python import socket # 创建UDP套接字 client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 客户端IP地址和端口号(可以随意指定,但必须与服务端不同) client_address = ('127.0.0.1', 8889) while True: # 发送数据给服务端 message = input('Enter message to send to server: ') client_socket.sendto(message.encode('utf-8'), ('127.0.0.1', 8888)) # 接收服务端发送的响应数据 data, server_address = client_socket.recvfrom(1024) print('Received message from {}: {}'.format(server_address, data)) ``` 在这个聊天程序中,服务端和客户端均使用UDP套接字进行通信。服务端首先创建一个UDP套接字,并绑定到指定的IP地址和端口号。然后进入一个无限循环,等待接收客户端发送的数据。当服务端接收到客户端发送的数据后,它会打印出接收到的消息,并等待用户输入响应消息,然后将响应消息发送给客户端。 客户端创建一个UDP套接字,并随意指定一个IP地址和端口号,然后进入一个无限循环,等待用户输入要发送的消息。当客户端输入消息后,它会将消息发送给服务端,并等待服务端的响应。当客户端接收到服务端的响应后,它会打印出接收到的消息。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值