socket编程基础,socket编程菜鸟教程

  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本人!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: