在大多数编程语言中,一般有相应的网络编程类。例如C#的System.Net,C++的”common.h”,还有Java的java.net. 不同语言对网络编程的实现事实上是大同小异的,都是通过一些常见的函数来实现。下面我们详细介绍几个相当重要的函数:
这个函数应该非常好理解,就是创建一个socket实例,一般程序设计语言对于初学者来说相关参数不用调整。其参数主要是指定协议类型,套接字类型以及协议号等,不同语言有些许差别。我们例子中主要使用TCP/IP协议,TCP/IP套接字类型以及0(默认)协议号。感兴趣的同学可以去了解一下计算机网络的相关知识会对网络编程有一个更为深刻的理解。下面给出C#代码的形式:
server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
这里的AddressFamily.InterNetwork指的是使用IPv4版本地址,SocketType.Stream指的是使用字节流传输,ProtocolType.Tcp指的是使用TCP协议。若学过计算机网络的相关课程应该对上述内容有十分充分的了解了,若没学过也没关系,什么都不用改,直接照上面新建一个socket就好。
在这里可能有人会问,socket不是包含IP地址和端口号吗,为什么这里两者均没有指定?这就与下一个函数息息相关了。
这个函数主要是绑定socket的本地地址。简单来说就是给上一个函数创建的socket绑定一个ip地址与端口号。一般包含的参数就是有socket描述符(说明是哪一个socket),IP地址以及端口号。下面给出C#代码的形式:
IPEndPoint endpoint = new IPEndPoint(IPAddress.Any, 2333);
server.Bind(endpoint);
由于C#代码本身的特性,其通过多行代码来实现,第一行是建立一个类似于socket的集合体,就是IP地址加端口号。值得注意的是这里用的并不是某一个具体的IP地址,而是一个IP地址的通配符IPAddress.Any. 这是由于bind函数绑定后是声明监听的IP地址。一个服务器一般是监听所有IP“来者不拒”,因此我们希望它是一个通配符而不是某一个具体的IP地址。若此处输入某一具体的IP地址则该服务器只会接受来自该IP地址的连接请求。第二个部分是端口号,此处用2333,事实上可以为0-65535中的任何一个数,不过若开发一些标准应用会有一定的规范,例如开发HTTP应用端口号是80. 具体可以去学习一下计算机网络应用层的知识。若不知道耶没关系,可以用4-5位数,因为较小的数一般是标准应用所使用的,而较大的数则是个人应用或其他应用可使用部分。
注意:该函数一般只有服务器会使用,客户端一般使用下面的connect函数直接指定IP地址+端口号。
该函数主要是用于服务器端开始处于监听状态,在bind绑定了IP地址与端口号后,事实上服务器还是不能被其他网络进程所发现,因为还没有真正去关系其他进程是否想要连接它,自然其他网络也不可能连接上该服务器网络进程。而这个listen()函数则是打开一个监听端口,此后就会留意网络环境中产生的一举一动,生怕遗漏了任何一个想要连接它的网络进程。而其参数也很简单,一般就是声明请求队列的大小,指的是最多可以有多少个客户同时和这个服务器连接。下面给出C#的实现代码:
server.Listen(50);
非常简单的一个函数,运行完该语句之后就开始正式留意网络中的一举一动了。
该函数主要是用于服务器接收到了一个连接请求后,由于连接是socket一对一的连接,例如一个客户端连接了上面的socket(129.204.252.166:2333)后其他客户端就不能再连接这个服务器的端口了,那么计算机的解决方法就是先用这个端口号接受了,然后我再从计算机新开辟一个端口号,这个端口号是由计算机控制的,不需要我们自己去控制。通过这个端口号我们可以与客户进行通信。这个通信分为阻塞的同步算法(Accept)与回调函数实现的异步算法(BeginAccept),至于两者之间的区别我们将会在后面算法实现过程中进行对比讲解。下图的解释会更为形象,此处先给出C#的实现代码:
server.BeginAccept(AcceptCallback, server);
其中AcceptCallback是对应的回调函数,意思是若接收到一个连接请求后会调用该函数来进行反馈。回调函数需自己定义,并可以起任何符合命名规则的名字。而server则是对应的socket,与前面的server含义相同,主要是传递给回调函数使其知道对应套接字的描述符。
下面我们用一个形象的图来解释对应四个函数的功能:
通过以上四个函数,实际上我们就已经可以成功“部署”服务器了。那么还有另一边:客户端呢?客户端当然也有其独特的函数:
该函数主要用于客户端与服务器对应端口号建立连接,只有建立连接后我们才能发送消息,也就是我们最早举例子中的“登录”操作,不过该操作比登录更为抽象一点,并不是我们输入账户和密码登录,而是计算机自行通过目标的IP地址和端口号实现“登录”,也就是建立连接。以下为C#实现代码:
int port = int.Parse(2333);
IPAddress ipaddress = IPAddress.Parse(129.204.252.166);
IPEndPoint endpoint = new IPEndPoint(ipaddress, port);
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
client.BeginConnect(endpoint, Connectcallback, client);
由于部分函数上面都已经提到过,因此我们此处直接给出一个完整的客户端连接服务器的相关代码。
第一行为给出我们目标连接的端口号,由于在服务器部署过程中我们使用端口号为2333,因此此处我们必须指定连接端口号为2333. 第二行为给出连接的IP地址,也就是我们花钱在腾讯云注册的IP地址,笔者的服务器公网IP则是129.204.252.166.第三行为构造一个类似于socket的结构体,用于在网络环境中唯一标识该服务器。第四行则是和之前一样构造一个socket。第五行是通过构造出来的socket以及提供IP地址,端口号以及回调函数的一个异步方法去连接服务器。此处Connectcallback为回调函数。至于什么是异步方法与回调函数将在后面进行详细讲解。
事实上,服务器部署做了一大堆事而客户端连接就的确是这样轻描淡写一句话就可以连接上了。剩下的就是信息的收与发:
该函数功能就是发送一段信息到对应已进行连接的对象,可以是服务器调用也可以是客户端进行调用。一般所包含的参数有send的内容以及内容的长度。由于已经建立了连接(TCP连接)因此它们之间的通信将会变成端到端的一对一通信。所以这个函数并不用再指出对象的IP地址与端口号等信息,直接可以进行通信。下面给出C#代码的实现:
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(“你好”);
server.Send(bytes);
由于之前我们建立连接的时候用的是流传输(stream,不知道也没关系),因此我们这里必须队String进行转换为byte[]格式进行传输,而编码方式则使用常用的UTF-8编码。这样就可以用server.send()把消息发给接收端了。
既然有发送则一定有接受。同样的,接受也分为同步方法和异步方法,同步方法为recv(),异步方法为BeginReceive(),它们的差别在后面会详细解释,此处重点介绍今后常用的异步方法BeginReceive(),其中包含的参数主要有存储空间标识符,接受长度以及回调函数等,该方法较为复杂,会涉及许多函数的相互调用,因此后面的实例中会详细描述。下面给出C#代码的实现过程:
buffer = new byte[100024];
newsocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallback, so);
由于一定要先建立连接之后才能进行接收消息,因此该函数通常是写在accept()的回调函数中,也就是说的确有网络进程请求连接了才会调用接收消息。而对应参数则有接收消息的存储空间标识符buffer,空间大小以及回调函数receivecallback等。通常由于recv()是一个阻塞方法,因此经常新开辟一个线程来进行消息的接收。而BeginReceive则是异步方法,自身会开辟一个线程来进行服务,通常用回调函数来进行控制,在回调函数中再次调用BeginReceive()即可实现无线循环。
本章节介绍了固定IP地址服务器的获取以及七个网络编程重要的通用函数,这七个函数可以说是网络编程最最基本的要素,通过这七个函数就可以实现简单的C/S架构之间的交互,对于这七个函数之间,客户端服务器是如下调用的,用一个图来说比较好理解:
实际流程与我们前面描述的差别不大,就不做更多的解释了,由以上基础,我们可以用C#实行一个最简单的C/S架构程序,探索网络编程的奥秘。