您的位置:首页 > 理论基础 > 计算机网络

黑马程序员 Socket网络编程--聊天室

2013-07-01 12:56 573 查看
------- Windows Phone 7手机开发.Net培训、期待与您交流! -------

一、 网络中进程之间如何通信?

首先解决的问题是:如何唯一标识一个进程,否则通信无从谈起。在本地,可以用进程的PID来唯一标识一个进程,而在网路中则行不通。TCP/IP协议族已解决了这个问题:网络层的“IP地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(Ip地址,协议,端口)就可以标识网络中的进程了,网络中的进程标识就可以利用这个标识与其他进程进行交互。

二、 什么是Socket

用于描述IP地址和端口,是一个通信链的句柄。通过Socket可以接收和发送网络上的数据。

三、 Socket一般应用模式(服务端和客户端)

1、 服务端的Socket(至少需要两个)

(1) 一个负责接收客户端连接请求(但不负责通信);

(2) 每成功接收到一个客户端的连接便在服务端产生一个对应的Socket。

2、 客户端的Socket

(1) 必须指定要连接的服务端ip和端口;

(2) 通过创建一个Socket对象来初始化一个到服务端的TCP连接。

四、 Socket的通讯过程

1、 服务端

a) 申请一个socket

b) 绑定到Ip地址和端口

c) 开启监听

d) 等待客户端的连接请求。

2、 客户端

a) 申请一个socket

b) 向服务器发起连接(指明Ip地址和端口号)。

五、 面向连接的套接字调用时序



六、 实例:聊天程序

功能:服务端和客户端互相收发消息,服务端可以接受多个客户端的连接请求,并与之通信。

1、服务端程序:

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
//引入命名空间
using System.Net;   //IPAddress、IPEndPoint类
using System.Net.Sockets;
using System.Threading;

namespace Server_END
{
public partial class Server_End : Form
{
public Server_End()
{
InitializeComponent();
//关闭对跨线程非安全代码调用的检查
Server_End.CheckForIllegalCrossThreadCalls =false;
}

//在文本框显示信息
void ShowMsg(string msg)
{
txtRecMsg.AppendText(msg + "\n");
}

//负责监听的Socket
Socket sockWatch = null;
//key:远程主机的ip、端口,value:负责和该主机通信的服务端Socket
Dictionary<string, Socket> dict = new Dictionary<string, Socket>();

//开启监听服务
private void btnStartService_Click(object sender, EventArgs e)
{
//获得文本框中的ip地址的对象
IPAddress ipAddr = IPAddress.Parse(txtIP.Text.Trim());
//创建包含ip和端口的网络节点对象
IPEndPoint localEP = new IPEndPoint(ipAddr, Convert.ToInt32(txtPort.Text.Trim()));
//创建 服务端 负责监听的Socket对象(使用ipv4,使用流式连接,使用TCP协议传输数据)
sockWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定IP和端口
sockWatch.Bind(localEP);
//开始监听
sockWatch.Listen(10);

ShowMsg("服务器已启动监听...");

//创建新线程,处理客户端的连接请求
Thread threadWath = new Thread(WatchAccp);
threadWath.IsBackground = true;
threadWath.Start();
}

/// <summary>
/// 监听连接请求
/// </summary>
void WatchAccp()
{
while (true)
{
//接受连接请求并创建新Socket
Socket sockCon = sockWatch.Accept();
//显示远程主机的ip和端口
lbOnline.Items.Add(sockCon.RemoteEndPoint.ToString());
//添加字典
dict.Add(sockCon.RemoteEndPoint.ToString(), sockCon);

ShowMsg("和客户端已建立连接:" + sockCon.RemoteEndPoint.ToString());

//为新Socket创建线程,负责和客户端通信
object obj = sockCon;
Thread threadCon = new Thread(RecMsg);
threadCon.IsBackground = true;
threadCon.Start(obj);
}
}

/// <summary>
/// 接收消息
/// </summary>
void RecMsg(object obj)
{
Socket sockCon = (Socket)obj;
//创建消息缓冲区
byte[] msgBuffer = new byte[1024 * 1024 * 2];
while (true)
{
//将接收的消息存放到缓冲区,并返回字节数
int count = sockCon.Receive(msgBuffer);
//将字节数据转换为string
string strRecMsg = Encoding.UTF8.GetString(msgBuffer);
ShowMsg(string.Format("接收自{0}:{1}", sockCon.RemoteEndPoint.ToString(), strRecMsg));
}
}

//发送消息
private void btnSend_Click(object sender, EventArgs e)
{
if (lbOnline.Text.Trim().Length > 0)
{
string clientEP = lbOnline.Text;
//从字典中查到和某客户端通信的Socket
Socket sockCon = dict[clientEP];
//将string转换为byte数据
byte[] msgBuffer = Encoding.UTF8.GetBytes(txtSndMsg.Text.Trim());
//发送消息
sockCon.Send(msgBuffer);
}
}
}
}


服务端代码分析:
a) Socket.Accept()成员方法,会阻断当前线程,所以需要创建一个新线程执行该操作。
b) 每当有一个客户端连接服务器时,就创建一个Socket负责和客户端通信。这里用Dictionary<string,Socket>泛型,存储两者的对应关系。服务端向客户端发消息时,从Dictionary字典中取出与客户端对应的服务端Socket,然后用该Socket发送消息;
c) Receive方法会阻断当前线程,因此服务端在监听到客户端的连接请求后,就创建一个新线程来执行新连接的通信,接收客户端的消息;
d) CheckForIllegalCrossThreadCalls=false:关闭跨线程访问控件的检查,这里threadWath线程访问了UI线程创建的TextBox控件txtRecMsg,默认是不允许的,所以这里设置该属性为false,就可以跨线程访问了。
2、 客户端代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Client_END
{
public partial class Client_End : Form
{
public Client_End()
{
InitializeComponent();
Client_End.CheckForIllegalCrossThreadCalls = false;
}

//在文本框显示信息
void ShowMsg(string msg)
{
txtRecMsg.AppendText(msg + "\r\n");
}

Socket sockClient = null;
private void btnConnect_Click(object sender, EventArgs e)
{
IPAddress ipAddr = IPAddress.Parse(txtIP.Text.Trim());
IPEndPoint remoteEP = new IPEndPoint(ipAddr, Convert.ToInt32(txtPort.Text.Trim()));
sockClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//
sockClient.Connect(remoteEP);
}

private void btnSend_Click(object sender, EventArgs e)
{
byte[] strSndMsg = Encoding.UTF8.GetBytes(txtSndMsg.Text.Trim());
sockClient.Send(strSndMsg);
ShowMsg("发送了:" + txtSndMsg.Text.Trim());
}
}
}

客户端代码分析:

a) 首先申请一个Socket,指定服务端的ip和端口,请求连接(Connect);

b) 客户端需要不断的接收服务端的消息,所以创建一个后台线程,不断的循环接收(Receive)消息,如果收到消息,显示出来;

c) 如果服务器关闭了连接,客户端报SocketException异常。捕获该异常,提示“服务器已断开连接”,然后不再继续接收服务端的消息。

六、 实例:异常处理

Server和Client建立连接后,先关闭Client窗口,服务器代码报异常:

“未处理:SocketException 远程主机强迫关闭了一个现有的连接。”

解决办法:try-catch捕获SocketException,然后关闭当前服务端Socket,并中断接收客户端消息。

服务端接收消息的部分代码如下:

void RecMsg(object obj)
{
Socket sockCon = (Socket)obj;
//创建消息缓冲区
byte[] msgBuffer = new byte[1024 * 1024 * 2];
while (true)
{
try
{
//将接收的消息存放到缓冲区,并返回字节数
int count = sockCon.Receive(msgBuffer);
//将字节数据转换为string
string strRecMsg = Encoding.UTF8.GetString(msgBuffer, 0, count);
ShowMsg("接收自" + sockCon.RemoteEndPoint.ToString() + ":" + strRecMsg);
}
catch (SocketException e)
{
//客户端断开了连接,关闭与之通信的服务端Socket
sockCon.Close();
//不再接收已关闭的客户端消息
break;
}
}
}
------- Windows Phone 7手机开发.Net培训、期待与您交流! -------
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: