《Windows-C语言构建网络聊天室(共13页).doc》由会员分享,可在线阅读,更多相关《Windows-C语言构建网络聊天室(共13页).doc(13页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、精选优质文档-倾情为你奉上Windows C语言构建网络聊天室利用C语言编写Windows应用程序有两种方式:一种是Windows C编程方式,另一种是Visual C+编程方式。在一般情况下,Visual C+编程方式编写的程序源代码量小、开发时的工作量小、工作难度也较小,但编译后的代码量较大,运行速度略低;而Windows C编程方式编写的程序源代码量虽然较大,但可执行代码效率高。随着技术的进步,Visual C+编程方式已被广泛采用,但象网络编程等一些对速度要求高、对硬件操作较多的程序,大多数还是用Windows C编程方式开发的。另外,学习Windows C程序设计,还有助于更深入地了
2、解Windows的内幕和Windows API。基本的网络编程都是建立在Winsock基础上的。Winsock是90年代初,为了方便网络编程,由Microsoft联合了其他几家公司共同制定的一套WINDOWS下的网络编程接口,它是通过C语言的动态链接库方式提供给用户及软件开发者的,主要由winsock.h头文件和动态链接库winsock.dll组成,目前有两个版本:Winsock1.1和Winsock2.0。作为网络编程接口,Winsock屏蔽了网络底层的复杂的协议和数据结构,使得编程人员对网络的操作变得非常简单,因此,在Win32平台上,访问众多的基层网络协议,Winsock是首选接口。用W
3、insock构建一个网络聊天室,有两种基本的方式:数据报方式和流方式。一、 面向无连接的数据报方式数据报方式又称无连接方式,对应的是UDP(User Datagram Protocol)协议。这种方式不提供数据无错保证,数据可能丢失或重复并且接收顺序混乱,后发出的报文可能会先收到,并且报文的长度是有限制的;不过,由于取消了重发校验机制,能够达到较高的通信速率,可以用于对数据可靠性要求不高的通信,如实时的语音、图像传送和广播消息等。和C语言一样,函数是Windows C编程的最基本的单位。不过,Windows C主要使用API函数,而网络编程则主要使用Winsock提供的API函数。数据方式构建
4、网络聊天室主要使用了以下几个函数:1WSAStartup():初始化。【函数原型】int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);【使用说明】每一个使用winsock的应用程序,都必须进行WSAStart函数调用,并且只有在调用成功之后才能使用其它的winsock网络操作函数。返回值:调用成功返回0;否则,返回出错信息。WversionRequired:表示欲使用的insock版本,这是一个WORD类型的整数,它的高位字节定义的是次版本号,低位字节定义的是主版本号。LpWSAData:是一个指向WSAD
5、ATA资料的指针。这个资料我们一般不使用。2Socket():创建一个Socket。【函数原型】SOCKET socket(int af,int type,int proctocol);【使用说明】Winsock网络通信的第一步通常就是调用这个函数。所有的通信在建立之前都有要创建一个Socket。该函数的功能与文件操作中的fopen()类似,返回值是由Winsock定义的一种数据类型SOCKET,它实际是一个整型数据,是Socket创建成功时,Windows分配给程序的Socket编号,后面调用传输函数时,可以把它像文件指针样引用。如果Socket建立失败,返回值WIVALID_SOCKET。
6、Af:指address family(地址族),一般都填AF_INET,表示是在Internet上的Socket;Type::是Socket的类型,当采用流连接方式时,用SOCK_STREAM;采用数据报文方式时,用SOCK_DGRAM。Proctocol:一般都有为0,表示对两种类型的Socket分别采用缺省的TCP和UDP传输协议。3Bind():为创建Socket指定通信对象。【函数原型】int bind ( SOCKET s, const struct sockaddr FAR* name, int namelen );【使用说明】成功创建了Socket之后,就应该选定通信的对象。首先
7、是自己的程序要与网上的哪台计算机通话;其次,在多任务的系统下,该台计算机上可能会有几个程序在工作,必须指出要与哪个程序通信。前者可以通过IP地址来确定,而后者则由端口号来确定的。一台计算机有65536个端口,端口号范围为065535,不同的通信程序使用不同的端口。不过,1024以下的端口号一般都已被一些常用的网络服务程序所占用,因此,编制自己的通信程序时,指定的端口号应大于1024。s:上一步创建Socket时创建好的套接字。name:是指向描述通信对象地址信息的结构体strict sockaddr_in的指针。namelen:name结构体的长度。Sockaddr_in的定义如下:struc
8、t sockaddr_in short sin_family; unsigned short sin_port; struct in_addr sin_addr; char sin_zero8;其中,sin_family是指一套地址族,它指定所要使用的通信协议,通常设为AF_INET;sin_port端口号;sin_addr是IP地址;而sin_zero8的作用,只是使该结构的大小和SOCKADDR结构大小相同。IP地址sin_addr结构定义如下:struct in_addr union struct u_char s_b1,s_b2,s_b3,s_b4; S_un_b; struct u_
9、short s_w1,s_w2; S_un_w; u_long S_addr; S_un;这样,对于一个IP地址,例如“192.168.0.1”,你可以用以下三种方法赋给一个sockaddr结构体(例如struct sockaddr_in m_addr;):方法1:m_addr.sin_addr.S_un.S_un_b.s_b1=192;m_addr.sin_addr.S_un.S_un_b.s_b2=168;m_addr.sin_addr.S_un.S_un_b.s_b3=0;m_addr.sin_addr.S_un.S_un_b.s_b4=1;方法2:m_addr.sin_addr.S_u
10、n.S_un_w.s_w1=(1688)|192;m_addr.sin_addr.S_un.S_un_w.s_w2=(18)|0;方法3:m_addr.sin_addr.S_un.S_addr=(124)|(016)|(1688)|192;为了更方便地赋值,winsock还为我们提供了一个函数inet_addr(),可以把用字符串表示的IP地址“192.168.0.1”直接赋给结构体m_addr:char * IP_String=”192.168.0.1”;m_addr.sin_addr.S_un.S_addr=inet_addr(IP_String);4recvfrom()/sendto()
11、:【函数原型】int recvfrom ( SOCKET s,char FAR* buf, int len,int flags,struct sockaddr FAR* from, int FAR* fromlen );int sendto (SOCKET s,const char FAR * buf, int len, int flags,const struct sockaddr FAR * to, int tolen);【使用说明】s:是连接用的socket。buf、len:发送或接收的数据包字符串的地址和长度。flags:一般取0。from、fromlen/to、tolen:含义和用法
12、与bind()中的相同,分别表示接收和发送数据的对象。5Closesocket():【函数原型】int closesocket ( SOCKET s);【使用说明】和关闭文件操作一样,socketd在使用以后,也要关闭。Internet上的聊天室程序一般都是Client/Server结构的,由服务器提供服务端连接响应,使用者通过客户端程序登录到服务器(面向接连的流方式),或直接向服务端发送报文(面向无连接的数据报方式)。相应地,聊天室程序也就分为服务器端和客户端两部分。面向无连接的数据报方式的程序流程图如图1所示:调用socket()创建一个监听Socket调用bind()监听socket指定
13、通讯对象调用recvfrom()和sendto()进行通讯调用closesocket()关闭socket调用WSAStartup()初化Winsock调用WSAStartup()初化Winsock调用closesocket()关闭socket调用recvfrom()和sendto()进行通讯调用socket()创建一个会话Socket服务器端客户端 图1面向无连接的数据报方式流程图可以为服务器端和客户端分别建立如图1、图2所示对话框: 图1服务器端 图2客户端相应的源程序见附件中的源程序Chat_Room1。二、面向连接的流方式流方式又称无连接方式,对应的是TCP(Transport Cont
14、rol Protocol)协议。在这种方式下,两个通信的应用程序之间先要建立一种连接链路,确定了这条链路之后,数据才能被正确接收和发送。流方式的特点是通信可靠,对数据有校验和重发的机制,通常用来做数据文件的传输如FTP、Telnet等。这种方式主要使用了以下几个函数:1Connect():【函数原型】int connect ( SOCKET s, const struct sockaddr FAR* name, int namelen);【使用说明】与通信对象建立连接,主要用在客户端。其中s、name和namelen的含义与使用方法和bind()相同。如果连接失败,该函数会返回SOCKET_E
15、RROR。2listen():【函数原型】int listen (SOCKET s,int backlog);【使用说明】对于服务器端程序,当申请到Socket,并指定通信对象为INADDR_ANY之后,就应该等待一个客户端程序的连接。当没有连接请求时,就进入等待状态,直至有一个请求到达为止。其中:s:是socket()创建的socket。backlog:等待连接的队列长度,可取15。如果当某个客户程序要求连接之时,服务器已与其他客户程序连接,则后来的连接请求会被放在队列中,等待服务器空闲的时候再与之连接。当队列达到指定长度(backlog的值)时,再来的连接请求都将被拒绝。3accept()
16、:【函数原型】SOCKET accept (SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);【使用说明】对与服务器端程序,在接收到一个连接请求之后,要为这个连接建立一个新的socket,这个任务由accept()函数来完成,并把它作为返回值。新建的Socket与原来的Socket有相同的特性,包括端口号。原来的Socket用于继续等待其他的连接请求,而新生成的Socket才是与客户端进行通信的实际Socket。一般将参数中的SOCKET称做“监听”Socket,它只负责接受连接,不负责通话;而accept返回的SOCKET则称为“会话”
17、Socket,它只负责与客户端通话。参数中的指针addr和addrlen用来返回客户机的sockaddr_in结构体,通过addr可得到客户机的IP地址和连接端口。使用方法则与bind()中的name和namelen相同。4recv()/send():【函数原型】建立连接后,用来接收和发送数据。其中:s:是连接用的socket。buf、len和flags的含义与作用方法与recvfrom()/connect()中的相同,分别表示接收和发送的数据包字符串的地址、长度和标志。 面向无连接的数据报方式的程序流程图如图1所示:调用WSAStartup()初化Winsock调用socket()创建一个监
18、听Socket调用bind()为监听Socket指定通讯对象调用listen()设置等待连接状态调用accept()接收连接并生成会话socket调用send()和recv()进行对话调用closesocket()关闭socket调用WSAStartup()初化Winsock调用socket()创建一个会话Socket调用connect()与服务器端连接调用send()和recv()进行对话调用closesocket()关闭socket阻塞,等待用户连接阻塞,等待用户发送数据服务器端客户端图2面向连接的流方式流程图分别为服务器端和客户端建立如图3、图4所示对话框: 图3 图4这是一个单方向传送
19、的面向连接的的流方式聊天程序,源程序附在附件源程序Chat_Room2中,源代码比较简单,就不在这里另作说明了。三、面向连接的异步模式在上面的流方式中,函数listen()要等到有客房端的连接请求或是出错时才能返回;recv()函数也要等到有数据发送过来的时候或是出错的时候才能返回。这时,如果网络拥挤或一次发送的数据量过大,交换的数据不能在短时间内传送完, 收发数据的函数就因此不能返回,我们把这种现象叫做阻塞。在阻塞期间,除了等待网络操作完成不能进行任何操作。为了解决这一问题,Winsock为我们提供了一种异步模式,在这种模式中,函数在被调用后立即返回,Winsock通过函数WSAAsyncS
20、elect()来实现非阻塞通信。方法是,由该函数指定某种网络事件(如有数据到达、可以发送数据、有程序请求连接等),当被子指定的网络事件发生时,由Winsock发送由程序事先约定的消息。程序中就可以根据这些消息做出相应的处理。WSAAsyncSelect(),它的原型是:int WSAAsyncSelect (SOCKET s,HWND hWnd,unsigned int wMsg,long lEvent);Socket在这个函数调用中被自动设成非阻塞方式,hWnd是接收Winsock消息的窗口句柄,wMsg是向窗口发出的消息名称,用户可以任意定义。LEvent是被指定的网络事件,如下表所示:网
21、络事件lEvent值说明FD_READ希望Socket收到数据时发送读的消息FD_WRITE希望Socket发送数据时发送写的消息FD_OOB希望OOB data到达时发送到达的消息FD_ACCEPT希望有连接到来时发送连接请求的消息FD_CONNECT希望完成连接时发送连接完成的消息FD_CLOSE希望接收Socket关闭的消息FD_QOS希望接收Socket服务质量(QoS)变化的消息FD_GROUP_QOS希望接收Socket服务质量(QoS)不变的消息FD_ROUTING_INTERFACE_CHANGE希望接收指定的地址路由接口变化的消息FD_ADDRESS_LIST_CHANGE希
22、望接收Socket协议族局部地址变化的消息需要注意的是,WSAAsyncSelect()的设定是针对某一个Socket的,也就是说,只有当被设定为异步模式的Socket事件发生时,才会发送这些信息。如果开启了很多Socket,而要让每个Socket都变成异步模式,那么就必须对每一个Socket都呼叫WSAAsyncSelect()来一一设定。有了异步模式之后,可建立如下的服务器端和客户端程序,对话框如图5、图6所示: 服务器端和客户端程序分别为:Chat_RoomS.h#defineSER_MESSAGEWM_USER+100#define MaxNumber 5intWSA_return;W
23、SADATAWSAData;SOCKETserver_hSocket;SOCKETListen_hSocket;struct sockaddr_inserver_addr;struct sockaddr_inListen_addr;int Listen_addrlen=sizeof(Listen_addr);intfromlen=sizeof(server_addr);BOOLserver_bErr;UINT server_uPort;char ShowText1024;charInputText256;charReceive_Text256;SOCKETnSocketMaxNumber;ch
24、ar*ClientIPMaxNumber;inti;charComeMsg30= 走进聊天室rn;charLeaveMsg30= 离开聊天室rn;charLeaveMsg130= 异常离开rn;BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam);Chat_RoomS.c#include#includeresource.h#includeChat_RoomS.hint APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInsta
25、nce, LPSTR lpCmdLine,int nCmdShow)DialogBox(hInstance,(LPCTSTR)DIALOG1,NULL,(DLGPROC)Hostname_ipDlgPro);return(TRUE);BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)switch(message)case WM_INITDIALOG:WSA_return=WSAStartup(0x0002,&WSAData);if(WSA_return!=0)MessageBox
26、(NULL,初始化失败!,警告!,MB_OK);WSACleanup();return TRUE;case SER_MESSAGE:switch(lParam)case FD_ACCEPT:Listen_hSocket=accept(server_hSocket,(LPSOCKADDR)&Listen_addr,&Listen_addrlen);for(i=0;i0)MessageBox(NULL,异步模式创建失败!,警告!,MB_OK);ClientIPi=inet_ntoa(Listen_addr.sin_addr);break;if(iMaxNumber)strcat(ShowText,
27、ClientIPi);strcat(ShowText,ComeMsg);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);for(i=0;iMaxNumber;i+)if(nSocketi!=0)send(nSocketi,ClientIPi,strlen(ClientIPi),0);send(nSocketi,ComeMsg,strlen(ComeMsg),0);return TRUE;case FD_READ:for(i=0;iMaxNumber;i+)if(nSocketi=wParam)recv(nSocketi,Receive_Text,256,0
28、);strcat(ShowText,ClientIPi);strcat(ShowText,:rn );strcat(ShowText,Receive_Text);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);for(i=0;iMaxNumber;i+)if(nSocketi!=0)send(nSocketi,ClientIPi,strlen(ClientIPi),0);send(nSocketi,:rn ,strlen(:rn ),0);send(nSocketi,Receive_Text,strlen(Receive_Text),0);return T
29、RUE;case FD_WRITE:return TRUE;case FD_CLOSE:for(i=0;iMaxNumber;i+)if(nSocketi=wParam)strcat(ShowText,ClientIPi);strcat(ShowText,LeaveMsg);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);closesocket(nSocketi);nSocketi=0;for(i=0;iMaxNumber;i+)if(nSocketi!=0)send(nSocketi,ClientIPi,strlen(ClientIPi),0);send
30、(nSocketi,LeaveMsg,strlen(LeaveMsg),0);return TRUE;default:for(i=0;iMaxNumber;i+)if(nSocketi=wParam)strcat(ShowText,ClientIPi);strcat(ShowText,LeaveMsg1);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);closesocket(nSocketi);nSocketi=0;for(i=0;i0)MessageBox(NULL,异步模式创建失败!,警告!,MB_OK);break;server_addr.sin_
31、family=AF_INET;server_addr.sin_addr.S_un.S_addr=INADDR_ANY;server_uPort=GetDlgItemInt(hDlg,IDC_PORTUINT,&server_bErr,TRUE);server_addr.sin_port=htons(u_short)server_uPort);if(bind(server_hSocket,(LPSOCKADDR)&server_addr,sizeof(server_addr)=SOCKET_ERROR)MessageBox(NULL,绑定端口失败!,警告,MB_OK);break;if(list
32、en(server_hSocket,5)=SOCKET_ERROR)MessageBox(NULL,侦听失败!,警告!,MB_OK);break;strcpy(ShowText,服务器建立成功!rn);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);return TRUE;case IDC_SHOWTEXT:if(HIWORD(wParam)=EN_CHANGE)if(strlen(ShowText)1024-256)strcpy(ShowText,);case IDC_INPUTTEXT:if(HIWORD(wParam)=EN_CHANGE)GetDl
33、gItemText(hDlg,IDC_INPUTTEXT,InputText,256);if(InputTextstrlen(InputText)-1=n)strcat(ShowText,管理员:rn );strcat(ShowText,InputText);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);for(i=0;iMaxNumber;i+)if(nSocketi!=0)send(nSocketi,管理员:rn ,strlen(管理员:rn ),0);send(nSocketi,InputText,strlen(InputText),0);strc
34、py(InputText,);SetDlgItemText(hDlg,IDC_INPUTTEXT,InputText);return TRUE;case IDCANCEL:WSAAsyncSelect(server_hSocket,hDlg,0,0);closesocket(server_hSocket);WSACleanup();EndDialog(hDlg,TRUE);return TRUE;return FALSE;Chat_RoomC.h#define CLI_MESSAGE WM_USER+200intlen;intWSA_return;WSADATAWSAData;SOCKETcl
35、ient_hSocket;struct sockaddr_inclient_addr;BOOLbErr;UINT client_uPort;char ClientIP20;char InputText256;charReceiveText256;charShowText1024;BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam);Chat_RoomC.c#include#includeresource.h#includeChat_RoomC.hint APIENTRY WinMa
36、in(HINSTANCE hInstance,HINSTANCE hPrevInstance, LPSTR lpCmdLine,int nCmdShow)DialogBox(hInstance,(LPCTSTR)DIALOG1,NULL,(DLGPROC)Hostname_ipDlgPro);return(TRUE);BOOL APIENTRY Hostname_ipDlgPro(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)switch(message)case WM_INITDIALOG:WSA_return=WSAStartup(0
37、x0002,&WSAData);if(WSA_return!=0)MessageBox(NULL,初始化失败!,警告!,MB_OK);WSACleanup();return TRUE;case CLI_MESSAGE:switch(lParam)case FD_READ:len=recv(client_hSocket,ReceiveText,256,0);ReceiveTextlen=0;strcat(ShowText,ReceiveText);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);return TRUE;case FD_WRITE:return
38、 TRUE;case FD_CLOSE:return TRUE;case FD_CONNECT:return TRUE;default:strcat(ShowText,网络错误,连接失败!);SetDlgItemText(hDlg,IDC_SHOWTEXT,ShowText);closesocket(client_hSocket);return TRUE;case WM_COMMAND:switch(LOWORD(wParam)case IDC_IPADDRESS:if(HIWORD(wParam)=EN_CHANGE)GetDlgItemText(hDlg,IDC_IPADDRESS,ClientIP,20);break;case IDC_INPUTTEXT:if(HIWORD(wParam)=EN_CHANGE)GetDlgItemText(hDlg,IDC_INPUTTEXT,InputText,256);if(InputTextstrlen(InputText)-1=n)send(client_hSocket,Inp