初学网络通信编程时应该注意的常见问题

首先让我们写一个C/S多线程网络程序,通过这个程序了解初学网络编程是应该注意的几个问题。

下面开始设计服务器端的业务逻辑代码:

首先,值得强调的是,要在该文件的顶端添加多线程和网络的命名空间语句:



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
//可以套接字
using System.Net.Sockets;
//可以使用多线程
using System.Threading;



接写来,设计类Threadtcpserver。代码如下:

class Threadtcpserver
{
private Socket server;
public Threadtcpserver()
{
//初始化IP地址
IPAddress local = IPAddress.Parse("127.0.0.1");
IPEndPoint iep = new IPEndPoint(local, 13000);
server = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
//将套接字与本地终结点绑定
server.Bind(iep);
//在本地13000端口号上进行监听
server.Listen(20);
Console.WriteLine("等待客户机进行连接...");
while (true)
{
//得到包含客户端信息的套接字
Socket client = server.Accept();
//创建消息线程服务对象
ClientThread newclient = new ClientThread(client);
//把ClientThread类的ClientService方法委托给线程
Thread newthread = new Thread(new ThreadStart(newclient.ClientService));
//启动消息服务进程
newthread.Start();

}

}

}

上面这段代码的业务逻辑是:

1.创建套接字Server,并将其与本地这结点iep进行绑定。然后,在13000端口上监听是否有新的客户端进行连接。

2.在无限循环中有一个阻塞的方法Accept(),该方法直到有新客户端连接到服务器上时,把客户端的套接字信息传递给client对象。否则,将阻塞直到有客户机进行连接。

3.ClientThread类负责处理客户端与服务器之间的通信。先把客户端的套接字句柄传递给负责消息服务的ClientThread类。然后,把ClientThread类的ClientService方法委托给线程,并启动线程。



然后开始编写ClientThread类的代码:

class ClientThread
{
//connection变量表示连接数
public static int connection = 0;
public Socket service;
int i;
//构造函数
public ClientThread(Socket clientsocket)
{
//service对象接管对消息的控制
this.service = clientsocket;
}

public void ClientService()
{
String data = null;
byte[] bytes = new byte[1024];
//如果Socket不是空,则连接数加1
if (service != null)
{
connection++;
}
Console.WriteLine("新客户建立连接:{0}个连接数", connection);
while ((i=service.Receive(bytes)) != 0)
{
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("收到的数据:{0}", data);
//处理客户端发来的消息,这里转化为大写字母
data = data.ToUpper();
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
//发送一条答应消息
service.Send(msg);
Console.WriteLine("发送的数据:{0}", data);
}
//关闭套接字
service.Close();
connection--;
Console.WriteLine("客户关闭连接:{0}个连接数", connection);
}



这段代码的业务逻辑:

1.在构造函数中得到接收到的客户端套接字client,以后就通过service句柄处理消息的接收和发送。

2.ClientService方法是委托给线程的,此方法进行消息的处理工作。在这里实现的功能是,先从客户端接收一条消息,然后把这条消息转换为大写字母,并立即发送一条应答的消息,也就是所谓的echo技术,通常用来进行消息之间的测试。

3.通过connections变量来记录活动连接数。当有新增连接或有断开连接的情况发生时,都会体现connection是的变化。



主函数代码如下:

static void Main(string[] args)
{
Threadtcpserver instance = new Threadtcpserver();
}

Main函数十分简单,生成一个Threadtcpsever实例,然后构造函数就会一步一步地展开,开始执行具体的业务逻辑。



编写完服务器端的程序后,只要再配置一个客户端程序即可。下面编写客户端代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;

namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
Socket client;
byte[] buf = new byte[1024];
string input;
IPAddress local = IPAddress.Parse("127.0.0.1");
IPEndPoint iep = new IPEndPoint(local, 13000);
try
{
client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
client.Connect(iep);
}
catch (SocketException)
{
Console.WriteLine("无法连接到服务器!");
return;
}
finally
{

}
while (true)
{
//在控制台上输入一条消息
input = Console.ReadLine();
//如果输入exit,可以断开服务器的连接
if (input == "exit")
{
break;
}
client.Send(Encoding.ASCII.GetBytes(input));
//得到实际收到的字节总数
int rec = client.Receive(buf);
Console.WriteLine(Encoding.ASCII.GetString(buf, 0, rec));
}
Console.WriteLine("断开与服务器的链接");
client.Close();
}
}
}

客户端是比较简单的,只有一个Main()函数。下面来分析一下这段代码的功能:

1.创建套接字,并通过connect方法连接到本地终结点。当连接建立以后,便可以与服务器进行通信了。

2.在客户端上等待用户输入一条信息,该消息会发送到服务器创建的消息服务线程上的ClientService方法进行处理。



运行界面:


让网络通信代码更强壮



通常,程序都需要进行后期维护和扩展。下面将通过在程序中加入异常处理的机制来加强该程序的健壮性。可以通过使用try和catch结构来包含可能会引发错误的代码。



try
{
client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
client.Connect(iep);
}
catch (SocketException)
{
Console.WriteLine("无法连接到服务器!");
return;
}
finally
{
Socket.close();
}



数据缓冲区处理方法

对于网络编程来说,如何处理好缓冲区这个问题一直是颇具有挑战性的。

1.合理的缓冲区处理

在多线程那段代码里,我们并没有对消息处理的细节做深入的分析,现在来看一下到底是怎么做的。

while ((i=service.Receive(bytes)) != 0)
{
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
Console.WriteLine("收到的数据:{0}", data);
//处理客户端发来的消息,这里转化为大写字母
data = data.ToUpper();
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
//发送一条答应消息
service.Send(msg);
Console.WriteLine("发送的数据:{0}", data);
}

这里的一个细节是,通过使用变量i得到实际从缓冲区取到的数据总数。在吧字节数组bytes转换成字符串data的过程中,可以清晰的看到,使用了GetString函数的带三个参数版本的重载方法。这样做有什么道理吗?如果把GetString方法的后两个参数去掉,会有什么结果?



不合理的缓冲区处理

在上文的代码中,去掉后两个参数并测试。


我们看到服务器端在接收到数据后多出了很多空行。

真正的原因是Recive方法会一次性把系统缓冲区内的内容取到数据缓冲区,得到数据量取决于缓冲区的大小,即bytes的大小。



如果把缓冲区大小设置的过小会出现什么状况呢?

我们把bytes重新设置:byte[] bytes = new byte[2];


当输入TEST时,系统会分两次进行数据传送。。即调用了两次Recive方法。



所以在处理缓冲区问题是,必须将问题考虑周全。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值