git地址:https://github.com/MyUsernameIsJX/WinSock
目录
1. 实验目的及环境
2. 实验要求
3. 实验内容及结果
4. 实验中遇到的问题以及解决方法
5. 实验总结
1、了解Winsock编程原理;
2、熟悉Windows网络编程接口;
Visual C或 C、VB等。
编写一个C/S通讯程序。
具体要求:
实验选择的是C#脚本语言
界面设计:
Server:
Client:
代码部分:
服务器代码
全局变量定义:
//服务器端的socket
private Socket serverSocket = null;
//服务器端线程
private Thread serverThread = null;
//保存已连接的客户端socket
private List clientSocketList = null;
//保存已开启的接收数据的客户端线程
private List clientThreadList = null;
// 接受数据缓冲区
private byte[] result = new byte[1024];
// 跨线程更新UI
Action AsyncUIDelegate;
// 服务器IP
private string IP = "";
// 服务器开放的端口
private int Port;
// 最大用户连接数
private int MAXCLIENTNUM = 5;
// 当前用户连接数
private int iClientNum = 0;
初始化控件:
// 将本机所有可用ip加入下拉框内
String[] ipAddress = getLocalAddress();
for(int i = 0;i
addMonitorMessage():
//向聊天框添加信息
private void addMonitorMessage(String message)
{
if("".Equals(tbMonitor.Text))
{
tbMonitor.Text = message + "\r\n";
}
else
{
tbMonitor.Text += message + "\r\n";
}
return;
}
getLocakAddress():
// 获得本机所有ip地址
private String[] getLocalAddress()
{
// 获得主机名
String hostName = Dns.GetHostName();
// 根据主机名查找ip
IPHostEntry iPHostEntry = Dns.GetHostEntry(hostName);
String[] result = new String[iPHostEntry.AddressList.Length];
int i = 0;
foreach (IPAddress iPAddress in iPHostEntry.AddressList)
{
result[i] = iPHostEntry.AddressList[i].ToString();
i++;
}
return result;
}
创建服务器:
// 启动服务器
private void btnStart_Click(object sender, EventArgs e)
{
addMonitorMessage("正在开启服务器......");
startServer();
btnStart.Enabled = false;
btnStop.Enabled = true;
}
private void startServer()
{
// 初始化客户端socket管理列表
clientSocketList = new List();
// 初始化客户端线程管理列表
clientThreadList = new List();
IP = cboIP.SelectedItem.ToString();
Port = Int32.Parse(tbPort.Text.ToString());
IPAddress iPAddress = IPAddress.Parse(IP);
serverSocket = new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
IPEndPoint pEndPoint = new IPEndPoint(iPAddress, Port);
serverSocket.Bind(pEndPoint);
serverSocket.Listen(10);
serverThread = new Thread(LitenerClientConnect);
serverThread.Start();
tbMonitor.Invoke(AsyncUIDelegate, "服务器启动成功!");
}
监听客户端的连接:
// 监听客户端的链接
private void LitenerClientConnect()
{
while(true)
{
Socket clientSocket = serverSocket.Accept();
// 将接受到的客户端socket添加到clientSocketList中
clientSocketList.Add(clientSocket);
// 获取客户端的IP和端口号
IPEndPoint clientIpEndPoint = clientSocket.RemoteEndPoint as IPEndPoint;
IPAddress clientIP = clientIpEndPoint.Address;
int clientPort = clientIpEndPoint.Port;
tbMonitor.Invoke(AsyncUIDelegate, "IP:" + clientIP.ToString()+"\t
端口:" + clientPort.ToString() +"\t已成功连接到服务器.");
// 向客户端发送信息
// 使用UTF8编码
clientSocket.Send(Encoding.UTF8.GetBytes("\t连接成功!\t本机IP:" +
clientIP.ToString() + "\t\t端口:" + clientPort.ToString()));
Thread receiverMessage = new Thread(receiverData);
clientThreadList.Add(receiverMessage);
receiverMessage.Start(clientSocket);
iClientNum++;
}
}
接受客户端的消息:
// 接受客户端的消息
private void receiverData(Object clientSocket)
{
Socket myClientSocket = (Socket)clientSocket;
while(true)
{
try // 防止服务器端接受消息堵塞
{
int receiverNum = myClientSocket.Receive(result);
string message = Encoding.ASCII.GetString(result, 0, receiverNum);
// 判断收到EXIT,如果是空则是客户端断开连接
if ("EXIT".Equals(message))
{
clientSocketList.Remove(myClientSocket);
iClientNum--;
IPEndPoint clientIpEndPoint =
myClientSocket.RemoteEndPoint as IPEndPoint;
IPAddress clientIP = clientIpEndPoint.Address;
int clientPort = clientIpEndPoint.Port;
tbMonitor.Invoke(AsyncUIDelegate, "IP:" + clientIP.ToString() +
"\t端口:" + clientPort.ToString() +"\t已断开连接.");
break;
}
else
{
for (int i = 0; i < clientSocketList.Count; i++)
{
clientSocketList[i].Send(Encoding.UTF8.GetBytes(message));
}
tbMonitor.Invoke(AsyncUIDelegate, message);
}
}
catch(Exception)
{
myClientSocket.Close();
break;
}
}
}
关闭服务器
private void btnStop_Click(object sender, EventArgs e)
{
addMonitorMessage("正在关闭服务器......");
stopServer();
btnStart.Enabled = true;
btnStop.Enabled = false;
}
// 关闭服务器
private void stopServer()
{
serverSocket.Close();
serverThread.Abort();
for (int i = 0; i < clientSocketList.Count; i++)
{
// 先检查连接到的客户端是否仍在连接,然后断开正在连接的客户端
if(clientSocketList[i].Connected == true)
{
clientSocketList[i].Disconnect(true);
}
clientSocketList.Remove(clientSocketList[i]);
}
// 关闭所有客户端线程
for (int i = 0; i < clientThreadList.Count; i++)
{
clientThreadList[i].Abort();
}
tbMonitor.Invoke(AsyncUIDelegate, "服务器关闭!");
btnStart.Enabled = true;
btnStop.Enabled = false;
}
获取GUI中的IP和Port
private void tbPort_TextChanged(object sender, EventArgs e)
{
Port = Int32.Parse(tbPort.Text.ToString());
}
private void cboIP_SelectedIndexChanged(object sender, EventArgs e)
{
IP = cboIP.SelectedItem.ToString();
}
添加新的客户端form:
private void btnAddClient_Click(object sender, EventArgs e)
{
if (iClientNum == MAXCLIENTNUM)
{
MessageBox.Show("用户数已达最大值,无法创建新的客户机!");
return;
}
Form clientForm = new Form2();
clientForm.Show();
}
客户端代码
所有全局变量以及含义:
// 客户端接收服务器消息线程
private Thread threadClient = null;
// 服务器socket
private Socket socketClient = null;
// 客户名字
private String clientName = "";
// 客户端端口
private int clientPort;
// 客户端IP
private IPAddress clientIP = null;
// 客户端IPEndPoint
private IPEndPoint clientIpEndPoint = null;
// 消息缓存区
byte[] recvBuffer = null;
初始化控件:
private void initView()
{
btnConnectToServer.Enabled = true;
btnExit.Enabled = false;
tbMonitor.ReadOnly = true;
tbIP.ForeColor = Color.Red;
tbPort.ForeColor = Color.Red;
tbName.ForeColor = Color.Red;
}
连接服务器:
// 连接服务器
private void btnConnectToServer_Click(object sender, EventArgs e)
{
try // 防止输入的信息出现错误 eg:port中含有字母
{
clientIP = IPAddress.Parse(tbIP.Text.ToString().Trim());
clientPort = Int32.Parse(tbPort.Text.ToString().Trim());
clientName = tbName.Text.ToString().Trim();
}
catch(Exception)
{
MessageBox.Show("输入信息不合法,请重新输入!");
return;
}
btnConnectToServer.Enabled = false;
btnExit.Enabled = true;
clientIpEndPoint = new IPEndPoint(clientIP, clientPort);
socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tbIP.ReadOnly = true;
tbPort.ReadOnly = true;
tbName.ReadOnly = true;
try
{
socketClient.Connect(clientIpEndPoint);
}
catch
{
MessageBox.Show("连接失败\r\n");
btnConnectToServer.Enabled = true;
btnExit.Enabled = false;
return;
}
threadClient = new Thread(recvMsg);
threadClient.Start();
}
接受服务器发来的消息:
// 接受服务器端发来的消息
private void recvMsg()
{
while(true)
{
try //
{
// 临时存储接收到的信息 1kb
recvBuffer = new byte[1024];
//将客户端套接字接收到的数据存入内存缓冲区,并获取长度
int length = socketClient.Receive(recvBuffer);
string strRecvMsg = Encoding.UTF8.GetString(recvBuffer, 0, length);
addMonitorMessage(strRecvMsg);
}
catch
{
addMonitorMessage("远程服务器已经中断连接\r\n");
btnConnectToServer.Enabled = true;
break;
}
}
}
客户端发送消息:
private void btnCSend_Click(object sender, EventArgs e)
{
try
{
string strMsg = tbSendMsg.Text.ToString().Trim();
if("".Equals(strMsg))
{
MessageBox.Show("消息不能为空!");
return;
}
if(btnConnectToServer.Enabled == true)
{
MessageBox.Show("请先连接到服务器,再发送消息");
return;
}
tbSendMsg.Text = "";
strMsg = "[" + clientName + "]:" + strMsg;
socketClient.Send(Encoding.UTF8.GetBytes(strMsg));
}
catch
{
MessageBox.Show("消息发送失败,请检查是否连接到服务器");
return;
}
}
断开连接:
private void btnExit_Click(object sender, EventArgs e)
{
try
{
// 通知服务器即将断开连接
socketClient.Send(Encoding.UTF8.GetBytes("EXIT"));
socketClient.Dispose();
socketClient.Close();
btnExit.Enabled = false;
btnConnectToServer.Enabled = true;
tbName.ReadOnly = false;
}
catch
{
MessageBox.Show("客户机关闭失败!");
return;
}
}
实验结果
服务器的初始化和启动:
添加客户以及连接服务器:
服务器发送消息:
客户端发送消息:
关闭客户端:
关闭服务器:
问题:
多线程程序中,新创建的线程不能访问UI线程创建的窗口控件,访问窗口的控件时无法对其控制。
解决方法:
将窗口构造函数中的CheckForIllegalCrossThreadCalls设置为false;然后就能安全的访问窗体控件。
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;
问题:
服务器创建时输入的ip可能不是本机的ip
解决方法:
将ip输入框改为下拉框,内容是本机所有可用ipv4的ip
问题:
传送消息之后,收到的中文显示乱码
解决方法:
发送消息时使用UTF8编码
clientSocketList[i].Send(Encoding.UTF8.GetBytes(message));
在调试的过程中,巩固提高了针对问题、错误的分析能力和逻辑思维能力。
学会了网络编程的一些基本知识。