启动服务端后,服务端通过持续监听客户端发来的请求,一旦监听到客户端传来的信息后,两端便可以互发信息了.服务端需要绑定一个IP,用于客户端在网络中寻找并建立连接.信息发送原理:将手动输入字符串信息转换成机器可以识别的字节数组,然后调用套接字的Send()方法将字节数组发送出去.信息接收原理:调用套接字的Receive()方法,获取对端传来的字节数组,然后将其转换成人可以读懂的字符串信息.
界面设计 -
服务端
IP文本框 name: txtIP port(端口号)文本框 name: txtPORT 聊天内容文本框 name: txtMsg 发送信息文本框 name:txtSendMsg
启动服务按钮 name: btnServerConn 发送信息按钮name: btnSendMsg
服务端代码:
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.Threading;
using
System.Net.Sockets;
using
System.Net;
namespace
ChatServer
public
partial
class
FServer : Form
public
FServer()
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls =
false
;
Thread threadWatch =
null
;
Socket socketWatch =
null
;
private
void
btnServerConn_Click(
object
sender, EventArgs e)
socketWatch =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipaddress = IPAddress.Parse(txtIP.Text.Trim());
IPEndPoint endpoint =
new
IPEndPoint(ipaddress,
int
.Parse(txtPORT.Text.Trim()));
socketWatch.Bind(endpoint);
socketWatch.Listen(20);
threadWatch =
new
Thread(WatchConnecting);
threadWatch.IsBackground =
true
;
threadWatch.Start();
txtMsg.AppendText(
"开始监听客户端传来的信息!"
+
"\r\n"
);
Socket socConnection =
null
;
private
void
WatchConnecting()
while
(
true
)
socConnection = socketWatch.Accept();
txtMsg.AppendText(
"客户端连接成功"
+
"\r\n"
);
ParameterizedThreadStart pts =
new
ParameterizedThreadStart(ServerRecMsg);
Thread thr =
new
Thread(pts);
thr.IsBackground =
true
;
thr.Start(socConnection);
private
void
ServerSendMsg(
string
sendMsg)
byte
[] arrSendMsg = Encoding.UTF8.GetBytes(sendMsg);
socConnection.Send(arrSendMsg);
txtMsg.AppendText(
"So-flash:"
+ GetCurrentTime() +
"\r\n"
+ sendMsg +
"\r\n"
);
private
void
ServerRecMsg(
object
socketClientPara)
Socket socketServer = socketClientPara
as
Socket;
while
(
true
)
byte
[] arrServerRecMsg =
new
byte
[1024 * 1024];
int
length = socketServer.Receive(arrServerRecMsg);
string
strSRecMsg = Encoding.UTF8.GetString(arrServerRecMsg, 0, length);
txtMsg.AppendText(
"天之涯:"
+ GetCurrentTime() +
"\r\n"
+ strSRecMsg +
"\r\n"
);
private
void
btnSendMsg_Click(
object
sender, EventArgs e)
ServerSendMsg(txtSendMsg.Text.Trim());
private
void
txtSendMsg_KeyDown(
object
sender, KeyEventArgs e)
if
(e.KeyCode == Keys.Enter)
ServerSendMsg(txtSendMsg.Text.Trim());
private
DateTime GetCurrentTime()
DateTime currentTime =
new
DateTime();
currentTime = DateTime.Now;
return
currentTime;
界面设计 - 客户端
IP文本框 name: txtIP Port文本框 name: txtPort 聊天内容文本框 name:txtMsg 发送信息文本框 name: txtCMsg
连接到服务端按钮 name: btnBeginListen 发送消息按钮 name: btnSend
客户端代码:
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.Sockets;
using
System.Threading;
using
System.Net;
namespace
ChatClient
public
partial
class
FClient : Form
public
FClient()
InitializeComponent();
TextBox.CheckForIllegalCrossThreadCalls =
false
;
Socket socketClient =
null
;
Thread threadClient =
null
;
private
void
btnBeginListen_Click(
object
sender, EventArgs e)
socketClient =
new
Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ipaddress = IPAddress.Parse(txtIP.Text.Trim());
IPEndPoint endpoint =
new
IPEndPoint(ipaddress,
int
.Parse(txtPort.Text.Trim()));
socketClient.Connect(endpoint);
threadClient =
new
Thread(RecMsg);
threadClient.IsBackground =
true
;
threadClient.Start();
private
void
RecMsg()
while
(
true
)
byte
[] arrRecMsg =
new
byte
[1024 * 1024];
int
length = socketClient.Receive(arrRecMsg);
string
strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 0, length);
txtMsg.AppendText(
"So-flash:"
+ GetCurrentTime() +
"\r\n"
+ strRecMsg +
"\r\n"
);
private
void
ClientSendMsg(
string
sendMsg)
byte
[] arrClientSendMsg = Encoding.UTF8.GetBytes(sendMsg);
socketClient.Send(arrClientSendMsg);
txtMsg.AppendText(
"天之涯:"
+ GetCurrentTime() +
"\r\n"
+ sendMsg +
"\r\n"
);
private
void
btnSend_Click(
object
sender, EventArgs e)
ClientSendMsg(txtCMsg.Text.Trim());
private
void
txtCMsg_KeyDown(
object
sender, KeyEventArgs e)
if
(e.KeyCode == Keys.Enter)
ClientSendMsg(txtCMsg.Text.Trim());
private
DateTime GetCurrentTime()
DateTime currentTime =
new
DateTime();
currentTime = DateTime.Now;
return
currentTime;
获取电脑本机IP的方法: 例如:本机IP:192.168.0.3(可能变动) 端口号port可以随便写:1-65535之间的任意整数都行
1.打开程序 点击运行
2.在运行栏里输入cmd指令
3.输入查看IP指令: ipconfig
4.获取当前IP: 192.168.0.3. 当然不同的地方 本机IP有可能不一样
程序运行展示:
首先 点击服务端的 启动服务按钮 聊天内容出现"开始监听客户端传来的信息!"
然后 点击客户端上的"连接到服务端"按钮 可以看见服务端上又出现了一行字 "客户端连接成功"
之后 便可以 两端进行通信了
这样一个简单的聊天程序就完成了~~~~:)
源代码下载
客户端下载
ChatClient.zip
服务端下载
ChatServer.zip
[第二篇-文件发送]
基于网友的提议,最近有点时间,便打算给之前的聊天程序增加一个功能-文件发送.
文件发送跟字符串信息发送的原理其实是一样的,都是通过将需要发送的数据转换成计算机可以识别的字节数组来发送.当然,计算机本身并不知道你发送的是字符串信息还是文件,所以我们首先需要告诉计算机哪个发送的是文件,哪个是字符串信息;这里分别给它们的字节数组附加了一个类型标识符:字符串信息的字节数组标识符为0,文件的字节数组标识符为1.当一端将文件发送过去后,另一端则首先判断发送过来的类型标识符(1或者0),然后再调用相应的方法将获取的字节数组转换成人可以看懂的字符串信息或文件.
界面设计 - 客户端
这里新增了3个控件,用于实现文件发送功能.
Textbox: 文件名name: txtFileName
Button: 选择文件name: btnSelectFile 发送文件name: btnSendFile
代码实施 - 客户端
首先,我们需要写一个选择发送文件的方法,这里使用了最常见OpenFileDialog方法,用于选取需要发送的文件.
string
filePath =
null
;
string
fileName =
null
;
private
void
btnSelectFile_Click(
object
sender, EventArgs e)
OpenFileDialog ofDialog =
new
OpenFileDialog();
if
(ofDialog.ShowDialog(
this
) == DialogResult.OK)
fileName = ofDialog.SafeFileName;
txtFileName.Text = fileName;
filePath = ofDialog.FileName;
选取文件之后,通过FileStream来读取文件字节数组,然后在读到的文件字节数组的索引为0的位置上增加了一个文件标识符1,目的是告知计算机该字节数组为文件字节数组.这里在向服务端发送文件的同时也发送了一个文件名(字符串信息),目的是在服务端成功接收文件后,自动将原文件名附加上去.
private
void
SendFile(
string
fileFullPath)
if
(fileFullPath ==
null
)
MessageBox.Show(
"请选择需要发送的文件!"
);
return
;
else
if
(fileFullPath !=
null
)
FileStream fs =
new
FileStream(fileFullPath, FileMode.Open);
byte
[] arrClientFile =
new
byte
[10 * 1024 * 1024];
int
realLength = fs.Read(arrClientFile, 0, arrClientFile.Length);
byte
[] arrClientSendedFile =
new
byte
[realLength + 1];
arrClientSendedFile[0] = 1;
Buffer.BlockCopy(arrClientFile, 0, arrClientSendedFile, 1, realLength);
ClientSendMsg(fileName);
socketClient.Send(arrClientSendedFile);
txtMsg.AppendText(
"天之涯:"
+ GetCurrentTime() +
"\r\n您发送了文件:"
+ fileName +
"\r\n"
);
代码实施 - 服务端
由于新增了一个类型标识符,这里便将之前服务端接收信息的方法稍微改了下. 当服务端接收到含有标识符为0的字节数组,则直接将字节数组转换成字符串,并附加到聊天信息文本框上.若接收到的字节数组含有标识符1(即文件),则调用保存文件的方法SaveFile()将其保存为原文件;
string
strSRecMsg =
null
;
private
void
ServerRecMsg(
object
socketClientPara)
Socket socketServer = socketClientPara
as
Socket;
while
(
true
)
byte
[] arrServerRecMsg =
new
byte
[10 * 1024 * 1024];
int
length = socketServer.Receive(arrServerRecMsg);
if
(arrServerRecMsg[0] == 0)
strSRecMsg = Encoding.UTF8.GetString(arrServerRecMsg, 1, length - 1);
txtMsg.AppendText(
"天之涯:"
+ GetCurrentTime() +
"\r\n"
+ strSRecMsg +
"\r\n"
);
if
(arrServerRecMsg[0] == 1)
SaveFile(arrServerRecMsg, length - 1);
SaveFile()方法里包含了FileStream的Write()方法,用于将接收到的文件字节数组保存为实际文件,这里Write()方法传入了3个参数,文件的字节数组,需要拷贝文件字节数组的初始位置以及拷贝的字节数组的长度
[具体介绍可以看这里]
.在获取到文件的同时,这里也获取了文件名(字符串信息),用于附加到另存为对话框的文件名上;同时截取了文件名的后缀,作为需要保存的文件类型.最后,在文件成功保存到服务端所在的计算机的同时,在聊天内容文本框中附加了成功接收的文件名和文件的保存路径.
private
void
SaveFile(
byte
[] arrFile,
int
fileLength)
SaveFileDialog sfDialog =
new
SaveFileDialog();
string
fileNameSuffix = strSRecMsg.Substring(strSRecMsg.LastIndexOf(
"."
));
sfDialog.Filter =
"(*"
+ fileNameSuffix +
")|*"
+ fileNameSuffix +
""
;
sfDialog.FileName = strSRecMsg;
if
(sfDialog.ShowDialog(
this
) == DialogResult.OK)
string
savePath = sfDialog.FileName;
FileStream fs =
new
FileStream(savePath, FileMode.Create);
fs.Write(arrFile, 1, fileLength);
string
fName = savePath.Substring(savePath.LastIndexOf(
"\\"
) + 1);
string
fPath = savePath.Substring(0, savePath.LastIndexOf(
"\\"
));
txtMsg.AppendText(
"SoFlash:"
+ GetCurrentTime() +
"\r\n您成功接收了文件"
+ fName +
"\r\n保存路径为:"
+ fPath +
"\r\n"
);
首先,启动服务端并持续监听客户端对其的连接,当客户端成功连接上服务端之后,两端便可以开始通信了.
首先我们需要获取连接客户端的IP和Port,并添加到客户端列表里作为每个客户端的唯一标识.然后通过相应客户端的Socket.Send()方法将信息发送出去.
服务端给客户端的信息群发与服务端给单个客户端的信息发送原理是一样的,通过遍历客户端列表里的所有客户端标识,然后把信息一个个发送出去.
界面设计 - 客户端
界面设计 - 服务端
代码实施 - 客户端
客户端没有做什么功能改进,这里就不贴代码了,需要看的学友可以到随笔后面下载源代码.
代码实施 - 服务端
这里将手动输入服务端IPv4地址改为了程序自动获取
public
IPAddress GetLocalIPv4Address()
IPAddress localIPv4 =
null
;
IPAddress[] ipAddressList = Dns.GetHostAddresses(Dns.GetHostName());
foreach
(IPAddress ipAddress
in
ipAddressList)
if
(ipAddress.AddressFamily == AddressFamily.InterNetwork)
localIPv4 = ipAddress;
continue
;
return
localIPv4;
为了方便后期给所有访问的客户端群发信息,我们需要用过通过监听客户端来获取所有访问客户端的IP地址和端口号,并组成每个访问客户端的唯一标识clientName 用于显示在客户端列表上;客户端唯一标识还有个作用就是服务端可以选择性的给单独某个客户端发送信息.
Dictionary<
string
, Socket> dicSocket =
new
Dictionary<
string
, Socket>();
Socket socConnection =
null
;
string
clientName =
null
;
IPAddress clientIP;
int
clientPort;
private
void
WatchConnecting()
while
(
true
)
socConnection = socketWatch.Accept();
catch
(Exception ex)
txtMsg.AppendText(ex.Message);
break
;
clientIP = (socConnection.RemoteEndPoint
as
IPEndPoint).Address;
clientPort = (socConnection.RemoteEndPoint
as
IPEndPoint).Port;
clientName =
"IP: "
+ clientIP +
" Port: "
+ clientPort;
lstClients.Items.Add(clientName);
dicSocket.Add(clientName, socConnection);
ParameterizedThreadStart pts =
new
ParameterizedThreadStart(ServerRecMsg);
Thread thread =
new
Thread(pts);
thread.IsBackground =
true
;
thread.Start(socConnection);
txtMsg.AppendText(
"IP: "
+ clientIP +
" Port: "
+ clientPort +
" 的客户端与您连接成功,现在你们可以开始通信了...\r\n"
);
服务端向客户端发送信息,在没有选择具体某个客户端的情况下,默认群发. 如果选择了具体某个客户端,则单独向该客户端发送信息.
private
void
ServerSendMsg(
string
sendMsg)
sendMsg = txtSendMsg.Text.Trim();
byte
[] arrSendMsg = Encoding.UTF8.GetBytes(sendMsg);
if
(!
string
.IsNullOrEmpty(lstClients.Text.Trim()))
dicSocket[lstClients.Text.Trim()].Send(arrSendMsg);
txtMsg.AppendText(
"您在 "
+ GetCurrentTime() +
" 向 IP: "
+ clientIP +
" Port: "
+ clientPort +
" 的客户端发送了:\r\n"
+ sendMsg +
"\r\n"
);
else
for
(
int
i = 0; i < lstClients.Items.Count; i++)
dicSocket[lstClients.Items[i].ToString()].Send(arrSendMsg);
txtMsg.AppendText(
"您在 "
+ GetCurrentTime() +
" 群发了信息:\r\n"
+ sendMsg +
" \r\n"
);
1.首先启动服务端并连接各个客户端