socket编程基础,socket编程菜鸟教程
代码在我的博客里,包括UDP和TCP发送和接受的六个cpp文件,一个基于MFC的局域网聊天小工具项目,以及这个小工具的所有运行时库、资源和执行器。该代码的压缩包的位置是http://www.blogjava.net/Files/wxb_nudt/socket_src.rar.
1前言
在一些常见的编程技术中,Socket网络编程可以说是最简单的一种。而且Socket编程对基础知识要求不高,适合初学者学习网络编程。目前支持网络传输的技术、语言和工具有很多,但大部分都是基于Socket开发的。尽管这些“高级”网络技术屏蔽了大部分底层实现,但它们声称极大地简化了开发。其实如果你没有一点Socket基础,理解和应用这些技术还是很困难的,会让你“一知半解”。
印象深刻的是,我在学习CORBA的时候,因为当时各方面基础比较薄弱,咀嚼了半年,最后还是无所适从。如果我现在带人去学CORBA,我一定会把顺序排好:先把C语法找出来;然后是VC编译环境或者nmake的使用;接下来学习一些基本的网络知识;然后Socket编程;这些大约需要3或4个月。有了这些基础,一周就可以了解CORBA,两个月就可以基于CORBA进行开发。
嗯,说了半天,其实只有一个中心思想。Socket非常简单易学!如果你懂C或者JAVA,对TCP、UDP等网络基础的机制略知一二,那么看完这篇文章就能熟练开发Socket了。
2 Socket介绍(摘自全文)
(本部分内容均抄袭自互联网,不保证正确性。有兴趣的可以看看!)
80年代初,美国政府高级研究工程局(ARPA)向加州大学伯克利分校提供资金,在UNIX操作系统下实现TCP/IP协议。在这个项目中,研究人员开发了一个用于TCP/IP网络通信的API(应用程序接口)。这个API叫做套接字接口(Socket)。如今,SOCKET接口是TCP/IP网络中最常用的API,也是互联网上应用开发最常用的API。
90年代初,微软和其他几家公司一起,制定了一套WINDOWS下的网络编程接口,即WindowsSockets规范。它是BerkeleySockets的重要扩展,主要是增加了一些异步功能,增加了一种符合Windows消息驱动特性的网络事件异步选择机制。WINDOWSSOCKETS规范是Windows下的开放式网络编程接口,支持多种协议。从1991年的1.0版到1995年的2.0.8版,经过不断的改进,在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已经成为Windows网络编程事实上的标准。目前在实际应用中主要有WINDOWSSOKCETS的1.1和2.0版本。两者最重要的区别是1.1版本只支持TCP/IP协议,而2.0版本可以支持多种协议。2.0版本具有良好的向后兼容性。任何使用1.1版的源代码、二进制文件和应用程序都可以在2.0规范下使用,无需修改。
SOCKET实际上是在计算机中提供了一个通信端口,通过这个端口你可以和任何一台有SOCKET接口的计算机进行通信。应用程序在网络上传输,接收到的信息通过这个SOCKET接口实现。在应用程序开发中,就像使用文件句柄一样,可以读写套接字句柄。
多说两句。
网上很多文章对Socket的来龙去脉都像教科书一样准确。但具体的编程技术往往会被VC等集成开发环境毒害,把Windows SDK、MFC、Socket、多线程、DLL、编译链接等技术混在一起,做成一锅生米。
既然要学习Socket,就要用最简单直白的方式讲出使用Socket的几个要点。我认为程序员最关心以下几点,按优先顺序排列:
1.Socket的机制是什么?
2.用C/C写Socket需要哪些头文件,库文件,dll?谁能提供它们?安装后它们通常在系统的哪个文件夹中?
3.写Socket程序需要什么编程基础?
4.套接字库中最重要的函数和数据类型是什么?
5.两个最简单的示例程序;
6.一个稍微复杂一点的Socket应用接近应用。
这几点我会一一讲,从简单到复杂,从浅显到花哨,全部给出源代码,以及编译链接的命令。
socket 4的机制是什么?
我们可以简单的把Socket理解为一个管道,可以连接网络上不同的计算机程序。如果你把一堆数据从A端扔进流水线,它会从流水线的B端出来(也许也可以从C、D、E、F …端出来)。管道的端口由两个因素唯一标识,即机器的IP地址和程序使用的端口号。大家都知道IP地址的意思。所谓端口号,就是一个程序员指定的数字。许多知名的特洛伊木马整天扫描网络上的不同端口号,以便获得连接的端口并摧毁它。众所周知的端口号是http端口80和ftp端口21(我是不是搞错了?)。当然,建议你自己写程序的时候不要用太小的端口号。它们一般被系统占用,不使用一些著名的端口。一般来说,最好使用1000~5000以内的端口。
套接字可以支持数据的发送和接收。它定义了一个名为socket的变量。发送数据时,首先创建套接字,然后使用套接字的sendto等方法将数据发送到一个IP/port。接收器也首先创建一个套接字,然后将套接字绑定到一个IP/端口。发送到此端口的所有数据都将被socket的recv等函数读取。就像从文件中读取数据一样。
5必需的头文件、库文件和dll
对于目前广泛使用的Windows Socket2.0版本,需要的一些文件如下(以VC6为例说明其物理位置):
l头文件winsock2.h,通常在c:程序文件 Microsoft visual studio vc98 包含;看这个头文件,我们可以看到它也包含了windows.h和pshpack4.h的头文件,所以可以使用windows中的一些常用APIs
库文件Ws2_32.lib通常位于c: program files Microsoft visual studio vc98 lib中;
L DLL文件Ws2_32.dll,通常在C:WINDOWSsystem32,这个可以猜到。
6编写Socket程序所需的编程基础
在开始编写套接字程序之前,您需要以下编程基础知识:
c语法;
l稍微了解一下windows SDK的基础,了解一些SDK的数据类型和API调用方法;
l一点点编译、链接和执行技术;只知道cl和link最常见的用法。
7 UDP
用最通俗的话来说,所谓UDP就是一个可以发出去,可以忽略的网络协议。因此,UDP编程的发送方可以只是发送,而不检查网络连接状态。下面的例子是用来说明如何编写UDP的,每个API和数据类型都会有详细的解释。
7.1UDP广播发送器
以下是使用UDP发送广播消息的示例。
#包含winsock2.h
#包含iostream.h
void main()
{
插座袜子;//socket套接字
char szMsg[]=这是一个UDP测试包;//要发送的字段
//1.启动套接字库,版本2.0
WORD wVersionRequested
WSADATA wsaData
int err
wVersionRequested=MAKEWORD( 2,0);
err=wsa startup(wVersionRequested,wsa data);
如果(0!=err )//检查套接字初始化是否成功。
{
Cout Socket2.0初始化失败,退出!;
返回;
}
//检查套接字库的版本是否为2.0
if (LOBYTE( wsaData.wVersion)!=2 HIBYTE( wsaData.wVersion)!=0 )
{
WSACleanup();
返回;
}
//2.创建一个套接字,
袜子=插座(
AF_INET,//互联网:UDP,TCP等
SOCK_DGRAM,//SOCK_DGRAM表示UDP类型
0//协议
);
if (INVALID_SOCKET==sock ) {
Cout 套接字创建失败,退出!;
返回;
}
//3.将套接字设置为广播类型,
bool opt=true
setsockopt(sock,SOL_SOCKET,SO_BROADCAST,reinterpret_cast char FAR * ( opt),sizeof(opt));
//4.设置发送地址
sockaddr _ in addrto//它被发送到的地址
memset( addrto,0,sizeof(addrto));
addrto.sin _ family=AF _ INET//地址类型是网际网络
addrto . sin _ addr . s _ addr=in addr _ BROADCAST;//将ip设置为广播地址
addrto . sin _ port=htons(7861);//端口号是7861
int nlen=sizeof(addrto);
unsigned int ui index=1;
while(真)
{
睡眠(1000);//程序休眠一秒钟
//向广播地址发送消息
if( sendto(sock,szMsg,strlen(szMsg),0,(sockaddr*) addrto,nlen)
==套接字_错误)
cout WSAGetLastError()endl;
其他
cout uIndex :发送了一个UDP包。endl
}
如果(!Closesocket(sock))//关闭套接字
{
WSAGetLastError();
返回;
}
如果(!WSACleanup()) //关闭套接字库
{
WSAGetLastError();
返回;
}
}
编译命令:
CL/c UDP _ Send _广播. cpp
命令(注意,如果找不到库,请在/LIBPATH参数后添加库的路径):
链接UDP _ Send _ broadcast . objws 2 _ 32 . lib
执行命令:
d:代码成品代码 socket socket _ srcudp _ send _ broadcast.exe
1:发送了一个UDP包。
2:发送一个UDP包。
3:发送了一个UDP包。
4:发送一个UDP包。
C
下面解释代码中出现的数据类型和API函数。有耐心的可以仔细看看,没耐心的可以画瓢写程序。
7.2插座类型
SOCKET是套接字类型,在WINSOCK2中定义如下。h:
typedef无符号int u _ int
typedef u_int套接字;
可以看出socket实际上是一个无符号整数,会被Socket环境管理和使用。套接字将被创建、设置、用于发送和接收数据,并最终被关闭。
7.3WORD类型、MAKEWORD、LOBYTE和HIBYTE宏
字类型是16位无符号整数,定义如下:
typedef无符号短词;
它的目的是提供两个字节的存储,可以在Socket中表示主版本号和次版本号。使用MAKEWORD宏为单词类型赋值。例如,要表示主版本号2和次版本号0,可以使用以下代码:
WORD wVersionRequested
wVersionRequested=MAKEWORD( 2,0);
请注意,下部存储器存储主版本号2,上部存储器存储次版本号0,值为0x0002。使用宏LOBYTE读取WORD的低位字节,使用HIBYTE读取高位字节。
7.4数据类型和LPWSADATA类型
WSADATA类型是一个结构,描述了Socket库的一些相关信息。其结构定义如下:
typedef结构WSAData {
WORD wVersion
WORD wHighVersion
char SZ description[wsa description _ l EN 1];
char szSystemStatus[wsa sys _ STATUS _ l EN 1];
无符号短iMaxSockets
无符号短整型iMaxUdpDg
char FAR * lpVendorInfo
} WSADATA
typedef WSADATA FAR * LPWSADATA
值得注意的是,wVersion字段存储了套接字的版本类型。WSADATA是wsadata的指针类型。它们不是程序员手动填写的,而是通过Socket的初始化函数WSAStartup读出的。
7.5瓦启动功能
WSAStartup函数用于初始化Socket环境,其定义如下:
int PASCAL FAR wsa startup(WORD wversion required,LPWSADATA LPWSADATA);
返回值为整数,调用方法为PASCAL(即标准类型,PASCAL等于__stdcall)。有两个参数,第一个参数是WORD type,表示套接字的版本号,第二个参数是WSADATA类型的指针。
如果返回值为0,则初始化成功;否则,它会失败。
7.6WSACleanup功能
这是套接字环境的出口函数。返回值0表示成功,SOCKET_ERROR表示失败。
7.7插座功能
套接字的创建功能,其定义为:
SOCKET PASCAL FAR socket (int af,int type,int protocol);
第一个参数是int af,表示网络地址族。目前只有一个值有效,即AF_INET,代表互联网地址族。
第二个参数是int type,代表网络协议类型,SOCK_DGRAM代表UDP协议,SOCK_STREAM代表TCP协议;
第三个参数是int协议,它指定了网络地址族的特殊协议。目前没用,可以赋0值。
返回值是SOCKET如果返回INVALID_SOCKET,将会失败。
7.8setsockopt函数
该函数用于设置套接字的属性。如果socket的属性设置不正确,数据的发送和接收都会失败。定义如下:
int PASCAL FAR setsockopt (SOCKET s,int level,int optname,
const char FAR * optval,int opt len);
返回值的类型为int,0表示成功,SOCKET_ERROR表示发生了错误。
第一个参数SOCKET s表示要设置的套接字;
第二个参数int level表示要设置的属性的级别。级别包含以下值:SOL_SOCKET表示套接字级别;IPPROTO_TCP代表TCP协议层,IPPROTO_IP代表IP协议层(后两种我没用过);
第三个参数int optname表示设置参数的名称,SO_BROADCAST表示允许发送广播数据的属性。其他属性请参考MSDN;
第四个参数const char FAR * optval表示指向存储参数值的指针。请注意,这里可能使用了reinterpret_cast类型转换;
第五个参数int optlen表示存储参数值的变量的长度。
7.9sockaddr_in,in_addr类型,inet_addr,inet_ntoa函数
Sockaddr_in定义了套接字发送和接收数据包的地址,并定义了:
struct sockaddr_in {
短sin _ family
u _ short sin _ port
结构地址sin _ addr
char sin _ zero[8];
};
其中in_addr定义如下:
结构输入地址{
工会
struct { u_char s_b1,s_b2,s_b3,s _ b4} S _ un _ b;
struct { u_short s_w1,s _ w2} S _ un _ w;
u _ long S _ addr
} S _ un
首先解释一下in_addr的含义。很明显是存储ip地址的联合体(忘记联合含义的请参考C本)。有三种表达方式:
第一种用四个字段表示IP地址的四位数;
第二个使用两个双字节来表示IP地址;
第三个使用一个长整数来表示IP地址。
给in_addr赋值最简单的方法之一就是使用inet_addr函数,它可以把一个表示IP地址的字符串赋值转换成in_addr类型,比如
addrto . sin _ addr . s _ addr=inet _ addr( 192 . 168 . 0 . 2 );
本例中未使用此功能,因为它是一个广播地址。它的反函数是inet_ntoa,可以把in_addr类型转换成字符串。
sockaddr_in的含义比in_addr的含义更广,各字段的含义和值如下:
第一个字段,简称sin_family,表示网络地址族。前面说过,它只能取值AF _ INET;
第二个字段u_short sin_port,代表IP地址端口,由程序员指定;
第三个字段structin_addr sin_addr表示IP地址;
第四个字段char sin_zero[8]填的很搞笑,保证SOCKADDR_in的长度等于SOCKADDR类型的长度。
以下代表表示广播地址,端口号为7861:
sockaddr _ in addrto//它被发送到的地址
memset( addrto,0,sizeof(addrto));
addrto.sin _ family=AF _ INET//地址类型是网际网络
addrto . sin _ addr . s _ addr=in addr _ BROADCAST;//将ip设置为广播地址
addrto . sin _ port=htons(7861);//端口号是7861
7.10 sockaddr类型
sockaddr类型用于表示套接字地址。与上面的sockaddr_in类型相比,sockaddr的适用范围更广,因为sockaddr_in只适用于TCP/IP地址。Sockaddr的定义如下:
结构sockaddr {
u _ short sa _ family
char sa _ data[14];
};
可以看到sockaddr有16个字节,sockaddr_in也有16个字节,所以sockaddr_in可以强制转换成sockaddr。事实上,这种方法经常被使用。
7.11睡眠功能
线程暂停功能表示线程暂停一段时间。Sleep(1000)的意思是挂起一秒钟。在WINBASE中定义。h头文件。WINBASE。h包含在WINDOWS中。h,然后是WINDOWS。h包含在WINSOCK2中。H.因此,本例中使用的Sleep函数不需要包含其他头文件。
7.12发送到功能
Socket中有两组发送和接收函数,一组是sendto和recvfrom;两个是发送和接收。在前一组中,地址应在函数参数中指明;第二套需要先把socket绑定到一个地址,然后直接发送和接收,不绑定地址。Sendto的定义如下:
int PASCAL FAR sendto (SOCKET s,const char FAR * buf,int len,int flags,const struct sockaddr FAR *to,int tolen);
第一个参数是套接字;
第二个参数是要传输的数据指针;
第三个参数是要传输的数据长度(字节数);
第四个参数是传输模式的标识,如果没有特殊要求可以设置为0。请参考MSDN;对于其他值;
第五个参数是目标地址。注意这里用的是sockaddr的指针;
第六个参数是地址的长度;
返回值是一个整数。如果成功,它将返回发送的字节数。如果失败,将返回SOCKET_ERROR。
7.13 WSAGetLastError函数
该函数用于读取Socket相关API失败后的错误代码。根据这些错误代码,可以检查出错误的原因。
7.14闭合插座
关闭套接字,其参数为套接字类型。成功返回0,失败返回SOCKET_ERROR。
7.15摘要
综上所述,编写UDP发送方的步骤如下:
1.用WSAStartup函数初始化Socket环境;
2.用socket函数创建一个套接字;
3.用setsockopt函数设置socket的属性,比如设置为广播类型;很多时候这一步可以省略;
4.创建一个sockaddr_in并指定其IP地址和端口号;
5.使用sendto函数向指定地址发送数据,其中目标地址为广播地址;注意,这里不需要绑定。即使绑定了,它的地址也会被sendto中的参数覆盖;如果使用send函数,会得到一个错误,因为send是面向连接的,而UDP是不连接的,所以只能使用send来发送数据。
6.用closesocket函数关闭套接字;
7.用WSACleanup函数关闭Socket环境。
然后,类似地,UDP接收器的步骤如下。请注意,接收方必须绑定套接字:
1.用WSAStartup函数初始化Socket环境;
2.用socket函数创建一个套接字;
3.用setsockopt函数设置socket的属性,比如设置为广播类型;
4.创建一个sockaddr_in并指定其IP地址和端口号;
5.用bind函数将接收到的地址与socket绑定,然后调用recvfrom函数或recv接收数据;注意这里必须绑定,因为接收消息的套接字在网络上必须有一个绑定名称,以保证正确的数据接收;
6.用closesocket函数关闭套接字;
7.用WSACleanup函数关闭Socket环境。
广播节目见源代码UDP_Recv_Broadcast.cpp。编译、链接和执行类似于UDP_Send_Broadcast。
7.16 UDP点对点发送和接收程序
广播和接收的使用并不广泛,一般来说,发送和接收的IP是常用的。对等UDP发送和接收与上面的示例非常相似,只是需要指定一个特定的IP地址。并且不需要调用setsockopt来设置socket的broadcast属性。
具体源代码见UDP_Send_P2P.cpp和UDP_Recv_P2P.cpp。
使用这两个程序时注意设置好自己需要的IP。
8 TCP
TCP和UDP最大的区别在于TCP是面向连接的协议。在发送和接收数据之前,必须连接TCP,并且在发送和接收期间必须保持连接。
发送方的步骤如下(省略Socket环境的初始化和关闭):
1.打造一只袜子;具有插座功能的et袜子;
2.用bind将sock绑定到本地地址;
3.用监听器监听袜子插座;
4.使用accept函数接收客户端的连接,并将客户端套接字返回给客户端套接字;
5.使用send在客户端套接字的clientSocket上发送数据;
6.closesocket套接字和clientSocket具有CloseSocket功能;
接收器的步骤如下:
1.打造一只袜子;具有插座功能的et袜子;
2.创建一个指向服务提供商的远程地址;
3.使用远程地址,用connect将sock连接到服务方;
4.使用recv在套接字上接收数据;
5.合上袜子;具有闭合式插座功能的et袜;
值得注意的是,服务端有两个地址,一个是本地地址myaddr,一个是目标地址addrto。本地地址myaddr用于绑定到本地socket sock,sock使用目标地址接受clientSocket客户端套接字。这样sock和clientSocket的连接就成功了,两个地址也连接起来了。在服务端使用clientSocket发送数据时,会从本地地址传输到目标地址。
客户端只有一个地址,就是源地址addrfrom。该地址用于连接远程服务套接字。如果连接成功,本地套接字将连接到远程源地址,因此您可以使用该套接字接收远程数据。实际上,此时客户端套接字已经被隐式绑定到本地地址,所以不需要显式调用bind函数,即使调用,结果也不会可视化。
详见TCP_Send.cpp和TCP_Recv.cpp。注意把源代码中的IP地址改成符合自己需求的IP。为了降低代码的复杂度,没有使用读取原生IP的代码,下面的示例程序包含了这个函数代码。
8.1绑定功能
bind函数用于将套接字绑定到IP地址。一般只在服务端(即数据发送方)调用,很多函数都是隐式调用bind函数。
8.2倾听功能
从服务端监控客户端的连接。同一个插座可以听多次。
8.3连接和接受功能
Connect是客户端连接到服务提供者的函数,accept是服务提供者同意客户端连接的函数。这两个支持函数在各自的程序中被成功调用后,可以发送和接收数据。
8.4发送和接收功能
Send和recv是用于发送和接收数据的两个重要函数。Send只能在连接状态下使用,recv可以在连接和未连接状态下使用。
发送的定义如下:
int WSAAPI发送(
插座s,
const char FAR * buf,
int len,
int标志
);
它的参数与sendto中的前四个参数具有相同的含义。recv定义如下:
int WSAAPI接收(
插座s,
char FAR * buf,
int len,
int标志
);
其参数含义与send中的含义相同。
9个局域网聊天工具的汇编
掌握了上面socket的基本用法,编写一个局域网聊天程序就变得非常简单,就像设计一个普通的对话程序一样。
9.1功能设计
这些功能如下:
1.能够指定聊天对象的IP和端口(端口可以内部确定);
2.能够向指定的聊天对象发送消息;
3.能够接收来自聊天对象的消息;
4.收到消息时播放声音;
5.收到消息时,如果当前对话框不是前端,闪烁图标;
6.要有托盘图标,可以把对话框放到托盘里;
9.2功能实现
将内部端口设置为3456,并提供一个IP地址控件来设置聊天对象的IP。该控件必须能够读取IP地址并将值赋给内部变量。将地址转换为in_addr类型。
你需要使用一个套接字来发送消息。
您还需要使用套接字来接收消息。因为你也是用socket来发送消息,为了同时在同一个进程中发送和接收消息,你需要使用多线程技术,将发送消息的线程设置为主线程。接收消息的线程设置为子线程,只负责接收UDP消息,接收后在主界面显示。
接收消息时播放声音的功能是在子线程中完成的,只需使用sndPlaySound函数并提供一个wav文件即可。
最白痴的闪烁图标的函数需要使用一个定时器,在主对话框类中添加一个OnTimer函数,定时检查当前窗口状态变量是否为假,如果是,每次设置另一个图标。如果当前窗口显示在顶部,它将被设置为默认图标。
托盘功能可以用从网上下载的CtrayIcon类轻松完成。您需要提供自定义消息和弹出菜单资源。
9.3所需资源
头文件:winsock2.h、Mmsystem.h
文件:ws2_32.lib,winmm.lib
dll:Ws2_32.dll,winmm.dll
Wav文件:recv.wav
图标:一个主程序图标IDI_MAIN和四个变化图标IDI _ icon 1 ~ 4;
菜单:托盘的弹出菜单IDR _ TRAYICON
说明:Mmsystem.h、winmm.lib、winmm.lib是为了播放声音的功能。
9.4托盘功能
托盘属于接口功能,是变化不大的需求,所以先完成。
1.两节课,特雷康。h和TRAYICON.cpp
2.将CTrayIconm_trayIcon添加到CLANTalkDlg类中;属性;
3.在CLANTalkDlg的构造函数中初始化m_trayIcon,M _ tray icon(IDR _ tray icon);
4.添加自定义消息WM_MY_TRAY_NOTIFICATION,即在三个地方添加消息定义、消息响应函数和消息映射;
5.调用InitDialog方法初始化的两个函数m _ tray icon . setnotification wnd(this,WM _ my _ tray _ notification);m_trayIcon。SetIcon(IDI _美因);
6.重写OnClose方法,添加弹出菜单的OnAppSuspend、OnAppOpen和OnAppAbout方法;
7.重写对话框的OnCancel方法。
9.5动态图标
动态图标也是界面相关的功能,应该先完成。
1.添加四个HICON变量m _ hicon1、m _ hicon2、m _ hicon3和m _ hicon4
2.在构造函数中初始化这四个变量m _ hi con 1=afxgetapp()-loadicon(idi _ icon 1);
3.在InitDialog中设置并调用SetTimer(1,300,NULL);设置一个定时器,id为1,间隔为300微秒;
4.添加一个布尔属性M _ BDDynamicIcon表示当前是否需要一个动态图标,并给出一个设置函数SetDynamicIcon;
5.添加Ontimer函数,在每次调用timer时根据M _ BDDynamicIcon的值修改图标;
有两个地方用来设置动态图标。一种是当程序接收到一条消息,程序不在桌面顶端时,设置为动态图标,会在后面的消息接收线程中处理。第二,当程序显示在桌面顶部时,设置为非动态;
重载OnActivate方法可以满足二阶矩的要求。当窗口状态为WA_ACTIVE或WA_CLICKACTIVE时,SetDynamicIcon(false);否则,设置SetDynamicIcon(true );
9.6发送UDP消息的功能
发送UDP消息只需要在主线程中完成,需要以下步骤:
1.初始化Socket环境,可以在CLANTalkApp的InitInstance中完成。类似地,关闭套接字环境可以在ExitInstance中完成;我们可以使用前面的方法或者直接调用MFC中的AfxSocketInit函数,这样可以保证Socket环境在程序结束时自动关闭;
2.创建socket,考虑到错误消息需要弹出对话框,所以没有在CLANTalkDlg的构造函数中创建,而是内置在InitDialog中;发送消息的套接字是M _ SendSock
3.要设置目的地址函数,需要一个地址分配函数set address(char * szAddr);您可以将字符串地址分配给sockaddr_in形式的地址;在CLANTalkDlg中添加一个sockaddr _ in m _ addrto属性;
4.读取文本框中的文本,用sendto发送到对象的地址;
5.清空文本框并在记录框中添加聊天记录。
这时可以用之前的UDP简单接收程序来辅助测试,因为此时消息接收功能还没有完成。
9.7接收UDP消息的功能
接收UDP消息时需要考虑几个问题。第一种是创建一个子线程,并在子线程中接收消息;第二,接收消息和发送消息要有互斥机制,避免冲突;三是收到消息后播放声音;四是在收到消息,当前窗口不在桌面顶部时调用动态图标函数。
根据上述要求的设计步骤如下:
1.创建一个接收套接字m_recvSock,
2.使用gethostname、gethostbyname等函数获取本地IP,并将套接字绑定到此地址;
3.添加CwinThread* m_pRecvThread属性,在InitDialog中调用AfxBeginThread创建子线程;
4.写子线程运行函数void RecvProcess(LPVOID pParam),这是一个全局函数。为了方便调用clantaltalgdlg类中的各种变量和方法,clantaltalgdlg类的指针作为参数传入子线程函数,RecvProcess设置为clantaltaldlg类的友元。
5.子线程函数执行以下功能:使用recv接收消息;保存聊天记录;判断当前窗口是否在前台,并修改动态图标属性;播放声音。
6.ClistBox用来记录聊天信息的Sort属性要去掉,否则记录会按内容排序,很难看。只需在RC编辑器中删除该属性。
7.最后需要注意的是,当主线程退出时,需要保证子线程退出,但此时子线程在recv方法上仍然被阻塞,所以主线程给自己发送消息消除阻塞,同时改变子线程的退出标志,保证子线程可以退出。
9.8设置聊天对象的IP
点击“确认对象”按钮时,检查IP地址控制,如果IP地址有效,将IP地址读入内部属性。该IP地址用作发送信息的目的地址。
此设置只能设置发送消息的对象。只要端口正确,每个人都可以向这台机器发送消息。
9.9编译链接并运行
下载完压缩包,就可以打开VC项目编译链接了。如果直接运行,可以点击LANTalkExeFile目录下的可执行文件。这个ta
当然,如果有必要的话,InstallShield也可以作为安装程序使用,但似乎没有必要。
9.10摘要
这个聊天程序很简单,但是基本上有一个框架,有最简单的聊天功能。在此基础上展开几乎没有技术问题。
使用好的Socket包可以简化开发过程。
文中所有的技术都要用最原始的方式。比如多线程用AfxBeginThread,socket用最原始的socket,很多地方直接用SDK函数,而尽量避免MFC等代码框架,就是为了方便别人掌握技术最基本的内涵。
其实在具体的编程中,当然是它有多方便。Socket、多线程、接口等函数都有大量方便可用的代码库。重用这些代码库会比自己写方便很多。但是,掌握了基本原理再去使用这些库,会事半功倍!
我实际上写了14页。是pf本人!