求教按键精灵脚本制作教程中的SOCKET通信

socket 的通信过程 - CSDN博客
socket 的通信过程
下图是基于TCP协议的客户端/服务器程序的一般流程:
服务器调用socket()、bind()、listen()完成初始化后,调用accept()阻塞等待,处于监听端口的状态,客户端调用socket()初始化后,调用connect()发出SYN段并阻塞等待服务器应答,服务器应答一个SYN-ACK段,客户端收到后从connect()返回,同时应答一个ACK段,服务器收到后从accept()返回。
数据传输的过程:
建立连接后,TCP协议提供全双工的通信服务,但是一般的客户端/服务器程序的流程是由客户端主动发起请求,服务器被动处理请求,一问一答的方式。因此,服务器从accept()返回后立刻调用read(),读socket就像读管道一样,如果没有数据到达就阻塞等待,这时客户端调用write()发送请求给服务器,服务器收到后从read()返回,对客户端的请求进行处理,在此期间客户端调用read()阻塞等待服务器的应答,服务器调用write()将处理结果发回给客户端,再次调用read()阻塞等待下一条请求,客户端收到后从read()返回,发送下一条请求,如此循环下去。
如果客户端没有更多的请求了,就调用close()关闭连接,就像写端关闭的管道一样,服务器的read()返回0,这样服务器就知道客户端关闭了连接,也调用close()关闭连接。注意,任何一方调用close()后,连接的两个传输方向都关闭,不能再发送数据了。如果一方调用shutdown()则连接处于半关闭状态,仍可接收对方发来的数据。
在学习socket API时要注意应用程序和TCP协议层是如何交互的: *应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段 *应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段
看图所示的socket 通信过程
图12.9 socket 的通信过程
1.建立套接字
在sys/socket.h中。
int socket(int family, int type, int protocol);
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,family参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的介绍从略,指定为0即可。
Linux在利用socket()系统调用建立新的套接字时,需要传递套接字的地址族标识符、套接字类型以及协议,其函数定义于net/socket.c中:
&& asmlinkagelong sys_socket(int family, int type, int protocol)
&&&&&&& struct socket *
&&&&&&&&retval = sock_create(family, type, protocol,&sock);
&&&&&&& if (retval & 0)
&&&&&&&&&&&&&&&&
&&&&&&&&retval = sock_map_fd(sock);
&&&&&&&&if (retval & 0)
&&&&&&&&&&&&&&&&goto out_
&&&&&&&&/* It may be already another descriptor 8) Not kernel problem. */
out_release:
&&&&&&&&sock_release(sock);
&& 实际上,套接字对于用户程序而言就是特殊的已打开的文件。内核中为套接字定义了一种特殊的文件类型,形成一种特殊的文件系统sockfs,其定义于net/socket.c:
&static struct vfsmount *sock_
&static DECLARE_FSTYPE(sock_fs_type, &sockfs&,sockfs_read_super, FS_NOMOUNT);
& &&在系统初始化时,要通过kern_mount()安装这个文件系统。安装时有个作为连接件的vfsmount数据结构,这个结构的地址就保存在一个全局的指针sock_mnt中。所谓创建一个套接字,就是在sockfs文件系统中创建一个特殊文件,或者说一个节点,并建立起为实现套接字功能所需的一整套数据结构。所以,函数sock_create()首先是建立一个socket数据结构,然后将其“映射”到一个已打开的文件中,进行socket结构和sock结构的分配和初始化。
新创建的 BSD socket 数据结构包含有指向地址族专有的套接字例程的指针,这一指针实际就是proto_ops 数据结构的地址。
BSD 套接字的套接字类型设置为所请求的 SOCK_STREAM 或 SOCK_DGRAM 等。然后,内核利用 proto_ops 数据结构中的信息调用地址族专有的创建例程。
之后,内核从当前进程的 fd 向量中分配空闲的文件描述符,该描述符指向的 file 数据结构被初始化。初始化过程包括将文件操作集指针指向由 BSD 套接字接口支持的 BSD 文件操作集。所有随后的套接字(文件)操作都将定向到该套接字接口,而套接字接口则会进一步调用地址族的操作例程,从而将操作传递到底层地址族,如图12.10所示。
实际上,socket结构与sock结构是同一事物的两个方面。如果说socket结构是面向进程和系统调用界面的,那么sock结构就是面向底层驱动程序的。可是,为什么不把这两个数据结构合并成一个呢?
我们说套接字是一种特殊的文件系统,因此,inode结构内部的union的一个成分就用作socket结构,其定义如下:
struct inode {
&&&&&&&&&struct socket&&&&&&&&&&&&socket_i;
由于套接字操作的特殊性,这个结构中需要大量的结构成分。可是,如果把这些结构成分全都放在socket结构中,则inode结构中的这个union就会变得很大,从而inode结构也会变得很大,而对于其他文件系统,这个union成分并不需要那么庞大。因此,就把套接字所需的这些结构成分拆成两部分,把与文件系统关系比较密切的那一部分放在socket结构中,把与通信关系比较密切的那一部分则单独组成一个数据结构,即sock结构。由于这两部分数据在逻辑上本来就是一体的,所以要通过指针互相指向对方,形成一对一的关系。
2.在 INET BSD 套接字上绑定(bind)地址
&为了监听传入的 Internet 连接请求,每个服务器都需要建立一个 INET BSD 套接字,并且将自己的地址绑定到该套接字。绑定操作主要在 INET 套接字层中进行,还需要底层 TCP 层和 IP 层的某些支持。将地址绑定到某个套接字上之后,该套接字就不能用来进行任何其他的通讯,因此,该 socket数据结构的状态必须为 TCP_CLOSE。传递到绑定操作的 sockaddr 数据结构中包含要绑定的 IP地址,以及一个可选的端口地址。通常而言,要绑定的地址应该是赋予某个网络设备的
IP 地址,而该网络设备应该支持 INET 地址族,并且该设备是可用的。利用 ifconfig 命令可查看当前活动的网络接口。被绑定的 IP 地址保存在 sock 数据结构的rcv_saddr 和 saddr 域中,这两个域分别用于哈希查找和发送用的 IP 地址。端口地址是可选的,如果没有指定,底层的支持网络会选择一个空闲的端口。
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用bind绑定一个固定的网络地址和端口号。bind()成功返回0,失败返回-1。
bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。前面讲过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。我们的程序中对myaddr参数是这样初始化的:
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为SERV_PORT,我们定义为8000。
当底层网络设备接受到数据包时,它必须将数据包传递到正确的 INET 和 BSD 套接字以便进行处理,因此,TCP维护多个哈希表,用来查找传入 IP 消息的地址,并将它们定向到正确的socket/sock 对。TCP 并不在绑定过程中将绑定的 sock 数据结构添加到哈希表中,在这一过程中,它仅仅判断所请求的端口号当前是否正在使用。在监听操作中,该 sock 结构才被添加到 TCP 的哈希表中。
3.在 INET BSD 套接字上建立连接(connect)
&&& 创建一个套接字之后,该套接字不仅可以用于监听入站的连接请求,也可以用于建立出站的连接请求。不论怎样都涉及到一个重要的过程:建立两个应用程序之间的虚拟电路。出站连接只能建立在处于正确状态的 INET BSD 套接字上,因此,不能建立于已建立连接的套接字,也不能建立于用于监听入站连接的套接字。也就是说,该 BSD socket 数据结构的状态必须为 SS_UNCONNECTED。
&在建立连接过程中,双方 TCP 要进行三次“握手”,具体过程在 本章第二节——网络协议一文中有详细介绍。如果 TCP sock 正在等待传入消息,则该 sock 结构添加到 tcp_listening_hash 表中,这样,传入的 TCP 消息就可以定向到该 sock 数据结构。
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。注意,客户端不是不允许调用bind(),只是没有必要调用bind()固定一个端口号,服务器也不是必须调用bind(),但如果服务器不调用bind(),内核会自动给服务器分配监听端口,每次启动服务器时端口号都不一样,客户端要连接服务器就会遇到麻烦。
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
4.监听(listen) INET BSD 套接字
int listen(int sockfd, int backlog);
典型的服务器程序可以同时服务于多个客户端,当有客户端发起连接时,服务器调用的accept()返回并接受这个连接,如果有大量的客户端发起连接而服务器来不及处理,尚未accept的客户端就处于连接等待状态,listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
& &当某个套接字被绑定了地址之后,该套接字就可以用来监听专属于该绑定地址的传入连接。网络应用程序也可以在未绑定地址之前监听套接字,这时,INET 套接字层将利用空闲的端口编号并自动绑定到该套接字。套接字的监听函数将 socket 的状态改变为 TCP_LISTEN。
当接收到某个传入的 TCP 连接请求时,TCP 建立一个新的 sock 数据结构来描述该连接。当该连接最终被接受时,新的 sock 数据结构将变成该 TCP 连接的内核bottom_half部分,这时,它要克隆包含连接请求的传入 sk_buff 中的信息,并在监听 sock 数据结构的 receive_queue 队列中将克隆的信息排队。克隆的 sk_buff 中包含有指向新 sock 数据结构的指针。
5.接受连接请求 (accept)
接受操作在监听套接字上进行,从监听 socket 中克隆一个新的 socket 数据结构。其过程如下:接受操作首先传递到支持协议层,即 INET 中,以便接受任何传入的连接请求。相反,接受操作进一步传递到实际的协议,例如TCP 上。接受操作可以是阻塞的,也可以是非阻塞的。接受操作为非阻塞的情况下,如果没有可接受的传入连接,则接受操作将失败,而新建立的 socket 数据结构被抛弃。接受操作为阻塞的情况下,执行阻塞操作的网络应用程序将添加到等待队列中,并保持挂起直到接收到一个 TCP 连接请求为至。当连接请求到达之后,包含连接请求的
sk_buff 被丢弃,而由 TCP 建立的新 sock 数据结构返回到 INET 套接字层,在这里,sock 数据结构和先前建立的新 socket 数据结构建立链接。而新 socket 的文件描述符(fd)被返回到网络应用程序,此后,应用程序就可以利用该文件描述符在新建立的 INETBSD 套接字上进行套接字操作。
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。cliaddr是一个传出参数,accept()返回时传出客户端的地址和端口号。addrlen参数是一个传入传出参数(value-result argument),传入的是调用者提供的缓冲区cliaddr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的地址。
注意:服务器接收到传入的请求后,如果能够接受该请求,服务器必须创建一个新的套接字来接受该请求并建立通讯连接(用于监听的套接字不能用来建立通讯连接),这时,服务器和客户就可以利用建立好的通讯连接传输数据。
转载地址:/kernel-book/ch12/12.3.4.htm
本文已收录于以下专栏:
相关文章推荐
Tcp通信基本流程:服务器端                      &#16...
Linux的SOCKET编程详解
1. 网络中进程之间如何通信
进 程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进
程之间既互不干扰又...
“一切皆Socket!”话虽些许夸张,但是事实也是,现在的网络编程几乎都是用的socket。——有感于实际编程和开源项目研究。我们深谙信息交流的价值,那网络中进程之间如何通信,如我们每天打开浏览器浏览...
游戏一般是基于若网连接的,多采用socket进行服务端与客户端的通信,今天我们了解一下C# 的socket知识。1、什么是Socket
socket 实际上是网络通信端点的一种抽象,它提供了一种发送...
Tcp通信基本流程:服务器端                      &#16...
Linux的SOCKET编程详解
1. 网络中进程之间如何通信
进 程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进
程之间既互不干扰又协调一致工作,操作...
在我们平时的开发中用到的最多的是HTTP协议,而HTTP协议本身是一种应用层协议,属于文本协议;并且这种协议也基本上满足了应用的大部分需求。HTTP协议当初的设计并没有想到它应用的是如此的广泛,所以设...
所谓socket 通常也称作&**套接字**&,用于描述IP地址和端口,是一个通信链的句柄。应用程序通常**通过&套接字&向网络发出请求或者应答网络请求**。
以J2SDK-1.3为例,Soc...
他的最新文章
讲师:何宇健
讲师:董岩
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)在说socket之前。我们先了解下相关的网络知识;
&在Internet上有很多这样的主机,这些主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务(应用程序)。
例如:http 使用80端口 ftp使用21端口 smtp使用 25端口
端口用来标识计算机里的某个程序   1)公认端口:从0到1023   2)注册端口:从   3)动态或私有端口:从4
Socket相关概念
socket的英文原义是&孔&或&插座&。作为进程通信机制,取后一种意思。通常也称作&套接字&,用于描述IP地址和端口,是一个通信链的句柄。(其实就是两个程序通信用的。)
socket非常类似于电话插座。以一个电话网为例。电话的通话双方相当于相互通信的2个程序,电话号码就是IP地址。任何用户在通话之前,
首先要占有一部电话机,相当于申请一个socket;同时要知道对方的号码,相当于对方有一个固定的socket。然后向对方拨号呼叫,
相当于发出连接请求。对方假如在场并空闲,拿起电话话筒,双方就可以正式通话,相当于连接成功。双方通话的过程,
是一方向电话机发出信号和对方从电话机接收信号的过程,相当于向socket发送数据和从socket接收数据。通话结束后,一方挂起电话机相当于关闭socket,撤消连接。
Socket有两种类型
流式Socket(STREAM): 是一种面向连接的Socket,针对于面向连接的TCP服务应用,安全,但是效率低;
数据报式Socket(DATAGRAM): 是一种无连接的Socket,对应于无连接的UDP服务应用.不安全(丢失,顺序混乱,在接收端要分析重排及要求重发),但效率高.
TCP/IP协议
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
应用层 (Application):应用层是个很广泛的概念,有一些基本相同的系统级 TCP/IP 应用以及应用协议,也有许多的企业商业应用和互联网应用。 解释:我们的应用程序
传输层 (Transport):传输层包括 UDP 和 TCP,UDP 几乎不对报文进行检查,而 TCP 提供传输保证。 解释;保证传输数据的正确性
网络层 (Network):网络层协议由一系列协议组成,包括 ICMP、IGMP、RIP、OSPF、IP(v4,v6) 等。 解释:保证找到目标对象,因为里面用的IP协议,ip包含一个ip地址
链路层 (Link):又称为物理数据网络接口层,负责报文传输。 解释:在物理层面上怎么去传递数据
你可以cmd打开命令窗口。输入
netstat -a
查看当前电脑监听的端口,和协议。有TCP和UDP
TCP/IP与UDP有什么区别呢?该怎么选择?
  UDP可以用广播的方式。发送给每个连接的用户   而TCP是做不到的
  TCP需要3次握手,每次都会发送数据包(但不是我们想要发送的数据),所以效率低   但数据是安全的。因为TCP会有一个校验和。就是在发送的时候。会把数据包和校验和一起   发送过去。当校验和和数据包不匹配则说明不安全(这个安全不是指数据会不会   别窃听,而是指数据的完整性)
  UDP不需要3次握手。可以不发送校验和
  web服务器用的是TCP协议
那什么时候用UDP协议。什么时候用TCP协议呢?   视频聊天用UDP。因为要保证速度?反之相反
下图显示了数据报文的格式
Socket一般应用模式(服务器端和客户端)
服务端跟客户端发送信息的时候,是通过一个应用程序 应用层发送给传输层,传输层加头部 在发送给网络层。在加头 在发送给链路层。在加帧
然后在链路层转为信号,通过ip找到电脑 链路层接收。去掉头(因为发送的时候加头了。去头是为了找到里面的数据) 网络层接收,去头 传输层接收。去头 在到应用程序,解析协议。把数据显示出来
TCP3次握手
在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。   第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize SequenceNumbers)。   第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;   第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
看一个Socket简单的通信图解
1.服务端welcoming socket 开始监听端口(负责监听客户端连接信息)
2.客户端client socket连接服务端指定端口(负责接收和发送服务端消息)
3.服务端welcoming socket 监听到客户端连接,创建connection socket。(负责和客户端通信)
服务器端的Socket(至少需要两个)
一个负责接收客户端连接请求(但不负责与客户端通信)
每成功接收到一个客户端的连接便在服务端产生一个对应的负责通信的Socket 在接收到客户端连接时创建. 为每个连接成功的客户端请求在服务端都创建一个对应的Socket(负责和客户端通信).
客户端的Socket
客户端Socket 必须指定要连接的服务端地址和端口。 通过创建一个Socket对象来初始化一个到服务器端的TCP连接。
Socket的通讯过程
服务器端:
申请一个socket 绑定到一个IP地址和一个端口上 开启侦听,等待接授连接
客户端: 申请一个socket 连接服务器(指明IP地址和端口号)
服务器端接到连接请求后,产生一个新的socket(端口大于1024)与客户端建立连接并进行通讯,原监听socket继续监听。
socket是一个很抽象的概念。来看看socket的位置
好吧。我承认看一系列的概念是非常痛苦的,现在开始编码咯
看来编码前还需要看下sokcet常用的方法
Socket方法: 1)IPAddress类:包含了一个IP地址 例:IPAddress& ip = IPAddress.Parse(txtServer.Text);//将IP地址字符串转换后赋给ip 2) IPEndPoint类:包含了一对IP地址和端口号 例:IPEndPoint point = new IPEndPoint(ip, int.Parse(txtPort.Text));//将指定的IP地址和端口初始化后赋给point 3)Socket (): 创建一个Socket 例:Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//创建监听用的socket 4) Bind(): 绑定一个本地的IP和端口号(IPEndPoint) 例:socket.Bind(point);//绑定ip和端口 5) Listen(): 让Socket侦听传入的连接尝试,并指定侦听队列容量 例: socket.Listen(10); 6) Connect(): 初始化与另一个Socket的连接 7) Accept(): 接收连接并返回一个新的socket 例:Socket connSocket =socket .Accept (); 8 )Send(): 输出数据到Socket 9) Receive(): 从Socket中读取数据 10) Close(): 关闭Socket (销毁连接)
首先创建服务端,服务端是用来监听客户端请求的。
创建服务器步骤:   第一步:创建一个Socket,负责监听客户端的请求,此时会监听一个端口   第二步:客户端创建一个Socket去连接服务器的ip地址和端口号   第三步:当连接成功后。会创建一个新的socket。来负责和客户端通信
1 public static void startServer()
//第一步:创建监听用的socket
Socket socket = new Socket
AddressFamily.InterNetwork, //使用ip4
SocketType.Stream,//流式Socket,基于TCP
ProtocolType.Tcp //tcp协议
//第二步:监听的ip地址和端口号
IPAddress ip = IPAddress.Parse(_ip);
//ip地址和端口号
IPEndPoint point = new IPEndPoint(ip, _point);
//绑定ip和端口
//端口号不能占用:否则:以一种访问权限不允许的方式做了一个访问套接字的尝试
//通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
socket.Bind(point);
catch (Exception)
if (new IOException().InnerException is SocketException)
Console.WriteLine("端口被占用");
//socket.Bind(point);
//第三步:开始监听端口
//监听队列的长度
/*比如:同时有3个人来连接该服务器,因为socket同一个时间点。只能处理一个连接
* 所以其他的就要等待。当处理第一个。然后在处理第二个。以此类推
* 这里的10就是同一个时间点等待的队列长度为10,即。只能有10个人等待,当第11个的时候。是连接不上的
socket.Listen(10);
string msg = string.Format("服务器已经启动........\n监听ip为:{0}\n监听端口号为:{1}\n", _ip, _point);
showMsg(msg);
Thread listen = new Thread(Listen);
listen.IsBackground = true;
listen.Start(socket);
观察上面的代码。开启了一个多线程。去执行Listen方法,Listen是什么?为什么要开启一个多线程去执行?
回到上面的 "Socket的通讯过程"中提到的那个图片,因为有两个地方需要循环执行
第一个:需要循环监听来自客户端的请求
第二个:需要循环获取来自客服端的通信(这里假设是客户端跟服务器聊天)
额。这跟使用多线程有啥关系?当然有。因为Accept方法。会阻塞线程。所以用多线程,避免窗体假死。你说呢?
看看Listen方法
1 /// &summary&
/// 多线程执行
/// Accept方法。会阻塞线程。所以用多线程
/// &/summary&
/// &param name="o"&&/param&
static void Listen(object o)
Socket socket = o as S
//不停的接收来自客服端的连接
while (true)
//如果有客服端连接,则创建通信用是socket
//Accept方法。会阻塞线程。所以用多线程
//Accept方法会一直等待。直到有连接过来
Socket connSocket = socket.Accept();
//获取连接成功的客服端的ip地址和端口号
string msg = connSocket.RemoteEndPoint.ToString();
showMsg(msg + "连接");
//获取本机的ip地址和端口号
//connSocket.LocalEndPoint.ToString();
如果不用多线程。则会一直执行ReceiveMsg
* 就不会接收客服端连接了
Thread th = new Thread(ReceiveMsg);
th.IsBackground = true;
th.Start(connSocket);
细心的你在Listen方法底部又看到了一个多线程。执行ReceiveMsg,对,没错。这就是上面说的。循环获取消息
ReceiveMsg方法定义:
/// &summary&
/// 接收数据
/// &/summary&
/// &param name="o"&&/param&
static void ReceiveMsg(object o)
Socket connSocket = o as S
while (true)
//接收数据
byte[] buffer = new byte[1024 * 1024];//1M
int num = 0;
//接收数据保存发送到buffer中
//num则为实际接收到的字节个数
//这里会遇到这个错误:远程主机强迫关闭了一个现有的连接。所以try一下
num = connSocket.Receive(buffer);
//当num=0.说明客服端已经断开
if (num == 0)
connSocket.Shutdown(SocketShutdown.Receive);
connSocket.Close();
catch (Exception ex)
if (new IOException().InnerException is SocketException)
Console.WriteLine("网络中断");
Console.WriteLine(ex.Message);
//把实际有效的字节转化成字符串
string str = Encoding.UTF8.GetString(buffer, 0, num);
showMsg(connSocket.RemoteEndPoint + "说:\n" + str);
提供服务器的完整代码如下:
2 using System.Collections.G
3 using System.L
4 using System.T
5 using System.Net.S
6 using System.N
7 using System.T
8 using System.IO;
9 namespace CAServer
class Program
//当前主机ip
static string _ip = "192.168.1.2";
static int _point = 8000;
static void Main(string[] args)
//Thread thread = new Thread(startServer);
//thread.Start();
startServer();
Console.ReadLine();
public static void startServer()
//第一步:创建监听用的socket
Socket socket = new Socket
AddressFamily.InterNetwork, //使用ip4
SocketType.Stream,//流式Socket,基于TCP
ProtocolType.Tcp //tcp协议
//第二步:监听的ip地址和端口号
IPAddress ip = IPAddress.Parse(_ip);
//ip地址和端口号
IPEndPoint point = new IPEndPoint(ip, _point);
//绑定ip和端口
//端口号不能占用:否则:以一种访问权限不允许的方式做了一个访问套接字的尝试
//通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
socket.Bind(point);
catch (Exception)
if (new IOException().InnerException is SocketException)
Console.WriteLine("端口被占用");
//socket.Bind(point);
//第三步:开始监听端口
//监听队列的长度
/*比如:同时有3个人来连接该服务器,因为socket同一个时间点。只能处理一个连接
* 所以其他的就要等待。当处理第一个。然后在处理第二个。以此类推
* 这里的10就是同一个时间点等待的队列长度为10,即。只能有10个人等待,当第11个的时候。是连接不上的
socket.Listen(10);
string msg = string.Format("服务器已经启动........\n监听ip为:{0}\n监听端口号为:{1}\n", _ip, _point);
showMsg(msg);
Thread listen = new Thread(Listen);
listen.IsBackground = true;
listen.Start(socket);
/// &summary&
/// 多线程执行
/// Accept方法。会阻塞线程。所以用多线程
/// &/summary&
/// &param name="o"&&/param&
static void Listen(object o)
Socket socket = o as S
//不停的接收来自客服端的连接
while (true)
//如果有客服端连接,则创建通信用是socket
//Accept方法。会阻塞线程。所以用多线程
//Accept方法会一直等待。直到有连接过来
Socket connSocket = socket.Accept();
//获取连接成功的客服端的ip地址和端口号
string msg = connSocket.RemoteEndPoint.ToString();
showMsg(msg + "连接");
//获取本机的ip地址和端口号
//connSocket.LocalEndPoint.ToString();
如果不用多线程。则会一直执行ReceiveMsg
* 就不会接收客服端连接了
Thread th = new Thread(ReceiveMsg);
th.IsBackground = true;
th.Start(connSocket);
/// &summary&
/// 接收数据
/// &/summary&
/// &param name="o"&&/param&
static void ReceiveMsg(object o)
Socket connSocket = o as S
while (true)
//接收数据
byte[] buffer = new byte[1024 * 1024];//1M
int num = 0;
//接收数据保存发送到buffer中
//num则为实际接收到的字节个数
//这里会遇到这个错误:远程主机强迫关闭了一个现有的连接。所以try一下
num = connSocket.Receive(buffer);
//当num=0.说明客服端已经断开
if (num == 0)
connSocket.Shutdown(SocketShutdown.Receive);
connSocket.Close();
catch (Exception ex)
if (new IOException().InnerException is SocketException)
Console.WriteLine("网络中断");
Console.WriteLine(ex.Message);
//把实际有效的字节转化成字符串
string str = Encoding.UTF8.GetString(buffer, 0, num);
showMsg(connSocket.RemoteEndPoint + "说:\n" + str);
/// &summary&
/// 显示消息
/// &/summary&
static void showMsg(string msg)
Console.WriteLine(msg);
//Console.ReadKey();
运行代码。显示如下
是不是迫不及待的想试试看效果。好吧其实我也跟你一样,cmd打开dos命令提示符,输入
telnet &192.168.1.2 8000
回车,会看到窗体名称变了
然后看到服务器窗口
然后在客户端输入数字试试
我输入了1 2 3 。当然,在cmd窗口是不显示的。这不影响测试。
小技巧:为了便于测试,可以创建一个xx.bat文件。里面写命令
telnet &192.168.1.2 8000
这样只有每次打开就会自动连接了。
当然。这仅仅是测试。现在写一个客户端,
创建一个winfrom程序,布局如下显示
请求服务器代码就很容易了。直接附上代码
2 using System.Collections.G
3 using ponentM
4 using System.D
5 using System.D
6 using System.L
7 using System.T
8 using System.Windows.F
9 using System.N
10 using System.Net.S
12 namespace WFAClient
public partial class Form1 : Form
public Form1()
InitializeComponent();
private void btnOk_Click(object sender, EventArgs e)
//客户端连接IP
IPAddress ip = IPAddress.Parse(tbIp.Text);
IPEndPoint point = new IPEndPoint(ip, int.Parse(tbPoint.Text));
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(point);
msg("连接成功");
btnOk.Enabled = false;
catch (Exception ex)
msg(ex.Message);
private void msg(string msg)
tbMsg.AppendText(msg);
private void btnSender_Click(object sender, EventArgs e)
//发送信息
if (socket != null)
byte[] buffer = Encoding.UTF8.GetBytes(tbContent.Text);
socket.Send(buffer);
* 如果不释放资源。当关闭连接的时候
* 服务端接收消息会报如下异常:
* 远程主机强迫关闭了一个现有的连接。
//socket.Close();
//socket.Disconnect(true);
运行测试,这里需要同时运行客户端和服务器,
首先运行服务器,那怎么运行客户端呢。
右键客户端项目。调试--》启用新实例
好了。一个入门的过程就这样悄悄的完成了。
以上内容来自:/nsky/p/4501782.html
根据上面的内容,已经可以开发出一个可以正常通信的Socket示例了,
接下来首先要考虑的就是服务器性能问题
1)在服务器接收数据的时候,定义了一个1M的Byte Buffer,有些设计的更大。更大Buffer可以保证客户端发送数据量很大的情况全部能接受完全。但是作为一个服务器每收到一条客户端请求,都要申请一个1M的Buffer去装客户端发送的数据。如果客户端的并发量很大的情况,还没等到网络的瓶颈,服务器内存开销已经吃不消了。
对于这个问题的解决思路是:
定义一个小Buffer,每次接受客户端请求用:
byte[] bufferTemp = new byte[1024];
和一个大Buffer,装客户端的所有数据,其中用到了strReceiveLength,是客户端发送的总长度,稍后再解释:
byte[] buffer = new byte[Convert.ToInt32(strReceiveLength)];
改写while (true)循环,每次接受1K的数据,然后用Array.Copy方法,把bufferTemp中的数据复制给buffer:
num = connSocket.Receive(bufferTemp, SocketFlags.None);
ArrayUtil.ArrayCopy(bufferTemp, buffer, check, num);
这个Array.Copy是重点,因为TCP数据流在传输过程中也是一个包一个包的传送,最大不超过8K。所以每次接受到的数据,也就是bufferTemp这个变量有可能装满,也有可能装不满。所以在拷贝的时候一定按照这次接受的长度顺序的放入buffer中。等到客户端全部数据发送完成后,再把buffer转换:
strReceive = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
而不能够每次都转换,再strReceive += 一个Byte数组。这样做的后果就是中文会被截断,因为中文在UTF-8编码下占3-4个字节,很容易出现乱码。
2)数据长度校验
TCP在传输过程中难免会有数据发送不全或者丢失的情况。所以在客户端发送数据的时候一定带上校验长度:
byte[] btyLength = Encoding.UTF8.GetBytes(strContent);
string strLength = btyLength.Length.ToString().PadLeft(8, '0');
string sendData = strLength + strC
byte[] buffer = Encoding.UTF8.GetBytes(sendData);
socketClient.Send(buffer);
这样在服务器端,先把要接受的长度收到:
byte[] bufferLength = new byte[8];num = connSocket.Receive(bufferLength);
strReceiveLength = Encoding.UTF8.GetString(bufferLength, 0, bufferLength.Length);
在循环里用下面的判断,来校验和判断是否已经接受完毕:
if (check == Convert.ToInt32(strReceiveLength))
3)设计上一些方式
很多局域网的部署是分层的,也就是分内网和外网。服务器部署一定要在外网上部署,这里的外网指的是在客户端之上的网段上。
比如192.168.1.22下有个无线路由,无线连接的IP段为192.168.2.1~254
服务器搭建在192.168.1网段下,192.168.2的客户端是可以访问的。但是相反则不行,192.168.1网段下的设备无法主动找到192.168.2的服务器。
阅读(...) 评论()

我要回帖

更多关于 按键精灵 通信 的文章

 

随机推荐