介绍
这篇文章将会介绍在.net框架下基本的socket编程。主要是使用c#,通过一个实例(服务器和客户端),借助TCP/IP协议和UDP协议来讲解。
1.网络基础
在网络通信中,多是通过连接多个主机来实现,而这其中,TCP/IP协议是最常见也最被认可的协议。在TCP/IP协议的控制下,每台主机由4字节的整数唯一的标识,以此作为他们的IP地址(比如192.168.0.101)。为了容易记忆,IP地址为被映射为一个好记的主机名。下面的程序(showip.cs)使用System.Net.Dns类来显示一个主机的IP地址,其中这台主机的主机名通过第一个参数传递。如果是缺省的参数,则显示本地主机的主机名和IP。
using System;
using System.Net;
class ShowIP{
public static void Main(string[] args){
string name = (args.Length < 1) ? Dns.GetHostName() : args[0];
try{
IPAddress[] addrs = Dns.Resolve(name).AddressList;
foreach(IPAddress addr in addrs)
Console.WriteLine("{0}/{1}",name,addr);
}catch(Exception e){
Console.WriteLine(e.Message);
}
}
}
Dns.GetHostName()返回本地主机的主机名,Dns.Resolve()通过一个主机名返回一台主机的IPHostEntry,以及这台主机的IP。函数Resolve将会抛出一个异常如果指定的主机没能找到。
尽管IPAddress可以在网络中指定一台主机,但是每一台主机都会运行着多个应用程序,他们都会进行网络数据交换。在TCP/IP下,每一个有网络事件的应用程序都会绑定一个由2字节整数构成的数,来代表他们的端口号,唯一标识了当前的应用程序。以字节为单位的数据交换成为IP包或数据报,大小为64KB,它包含了传输数据、数据的大小、发送方和接收方的IP地址和端口。当一个数据报在网络上传输时,它会被所有其他的主机接收,但是,只有数据报中指明的那个接收IP的主机才能实际上收到。然后这个主机再将数据报传给指定的端口所代表的应用程序。
TCP/IP实际上提供了两种数据交换协议,Transmission Control Protocol (TCP)是可靠地面向连接的协议,User Datagram Protocol (UDP) 是一种无连接的(但更快)的协议。
2.使用TCP/IP协议进行客户端-服务器编程
在TCP协议下,客户端和服务器的处理方式是有很大区别的。服务器以一个大家都知道的端口号开启服务,然后开始监听进来的连接请求,客户端可以已任意端口号开始,并发出一个连接请求。
服务器基本步骤如下:
(1)根据给定的端口号创建一个System.Net.Sockets.TcpListener
,并且运行它:
TcpListener listener = new TcpListener(local_port);
listener.Start();
(2)等待进来的连接请求,当有请求发生时,从“监听者”处接受一个System.Net.Sockets.Socket
对象:
Socket soc = listener.AcceptSocket(); // blocks
(3)使用上面的socket创建一个System.Net.Sockets.NetworkStream:
Stream s = new NetworkStream(soc);
(4)使用事先预定好的数据交换格式和客户端进行通信:
(5)关闭Stream
:
s.Close();
(6)关闭Socket
:
soc.Close();
(7)循环至第(2)步
注意,当一个请求在第(2)步发生时,其他请求是不能被接收的,除非第(7)步执行完。请求将会被放入队列中等待。为了可以同时接收、处理更多的客户端连接,第(2)-(7)步应该以多线程的方式执行。下面的程序(emptcpserver.cs)是一个多线程的TCP/IP服务器,它可以从客户端接收雇员的名字,然后向客户端发送相应的雇员的职位。客户端可以在发送雇员名字时发送一个空行来结束当前的网络通信连接。雇员的信息数据被保存在应用程序的配置文件中(在应用程序的所在路径的一个XML文件,文件名是应用程序文件名+.config)。
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Configuration;
class EmployeeTCPServer{
static TcpListener listener;
const int LIMIT = 5; //5 concurrent clients
public static void Main(){
listener = new TcpListener(2055);
listener.Start();
#if LOG
Console.WriteLine("Server mounted,
listening to port 2055");
#endif
for(int i = 0;i < LIMIT;i++){
Thread t = new Thread(new ThreadStart(Service));
t.Start();
}
}
public static void Service(){
while(true){
Socket soc = listener.AcceptSocket();
//soc.SetSocketOption(SocketOptionLevel.Socket,
// SocketOptionName.ReceiveTimeout,10000);
#if LOG
Console.WriteLine("Connected: {0}",
soc.RemoteEndPoint);
#endif
try{
Stream s = new NetworkStream(soc);
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true; // enable automatic flushing
sw.WriteLine("{0} Employees available",
ConfigurationSettings.AppSettings.Count);
while(true){
string name = sr.ReadLine();
if(name == "" || name == null) break;
string job =
ConfigurationSettings.AppSettings[name];
if(job == null) job = "No such employee";
sw.WriteLine(job);
}
s.Close();
}catch(Exception e){
#if LOG
Console.WriteLine(e.Message);
#endif
}
#if LOG
Console.WriteLine("Disconnected: {0}",
soc.RemoteEndPoint);
#endif
soc.Close();
}
}
}
下面的是配置文件的内容:
<configuration>
<appSettings>
<add key = "john" value="manager"/>
<add key = "jane" value="steno"/>
<add key = "jim" value="clerk"/>
<add key = "jack" value="salesman"/>
</appSettings>
</configuration>
在#if LOG 和#endif之间的是编译器加上的,如果标识符LOG被定义了,就可以在调试时看到。
下面说一下创建一个基于TCP/IP协议的服务器:
(1)根据服务器的主机名和端口号创建一个System.Net.Sockets.TcpClient:
TcpClient client = new TcpClient(host, port);
(2)从上面创建的TCPClient获得stream:
Stream s = client.GetStream()
(3)使用预定的数据交换格式和服务器通信
(4)关闭Stream:
s.Close();
(5)断开连接:
client.Close();
下面的代码(emptcpclient.cs)可以和上面的服务器(EmployeeTCPServer
)通信:
using System;
using System.IO;
using System.Net.Sockets;
class EmployeeTCPClient{
public static void Main(string[] args){
TcpClient client = new TcpClient(args[0],2055);
try{
Stream s = client.GetStream();
StreamReader sr = new StreamReader(s);
StreamWriter sw = new StreamWriter(s);
sw.AutoFlush = true;
Console.WriteLine(sr.ReadLine());
while(true){
Console.Write("Name: ");
string name = Console.ReadLine();
sw.WriteLine(name);
if(name == "") break;
Console.WriteLine(sr.ReadLine());
}
s.Close();
}finally{
// code in finally block is guranteed
// to execute irrespective of
// whether any exception occurs or does
// not occur in the try block
client.Close();
}
}
}