C# Socket服务端与客户端通信(包含大文件的断点传输)

  • Post author:
  • Post category:其他



步骤:


一、服务端的建立


1.服务端的项目建立以及页面布局


2.各功能按键的事件代码


1)传输类型说明以及全局变量


2)Socket通信服务端具体步骤:


(1)建立一个Socket


(2)接收信息


(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)


二、客户端的建立


1.服务端的项目建立以及页面布局


2.各功能按键的事件代码


1)传输类型说明以及全局变量


2)Socket通信服务端具体步骤:


(1)建立一个Socket


(2)接收信息


(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)

注意:此图是Socket通信的精华,在使用Socket通信时,有什么迷惑的可以看看此图,下面我们讲解的时候也是参照此图

Socket大家肯定很熟悉,对已内部的通信逻辑,肯定也有一定得了解—

对于Socket研究了两天写了一个小程序,通过Socket服务端与客户端的通信,以及大文件之间断点的传输(这里只做了服务端给客户端传送大文件,如果想把客户端的大文件传送给服务端也是一样的道理,看了文章,大家肯定可以自己实现)······

(自己才疏学浅,如有bug请谅解,但功能还是能实现的)

下面根据步骤进入正题:


一、服务端的建立


1.服务端的项目建立以及页面布局

新建解决方案“Socket通信”以及两个Winform项目(1)SockeClient——客户端    (2)SocketServer——服务器

给服务端界面布局——参照上图(这个大家肯定都是手到擒来就不累赘了······)


2.各功能按键的事件代码

先把整个服务端的代码贴出来,然后我们在一一讲解

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343


namespace


SocketServer


{




public


partial


class


Form1 : Form




{




//说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息




// 0:表示传递的是字符串信息




// 1:表示传递的是文件信息




// 2:表示的是震动




/// <summary>




/// 用来存放连接服务的客户端的IP地址和端口号,对应的Socket




/// </summary>




Dictionary<


string


, Socket> dicSocket =


new


Dictionary<


string


, Socket>();




public


Form1()




{




InitializeComponent();




}




private


void


Form1_Load(


object


sender, EventArgs e)




{




//不检测跨线程之间的空间调用




Control.CheckForIllegalCrossThreadCalls =


false


;




}




/// <summary>




/// 开启监听




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnStart_Click(


object


sender, EventArgs e)




{




try




{




//当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket




Socket socketWatch =


new


Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);




//获取IP




IPAddress ip = IPAddress.Any;




//创建端口号




IPEndPoint port =


new


IPEndPoint(ip, Convert.ToInt32(txtPort.Text));




//监听




socketWatch.Bind(port);




ShowMsg(


"监听成功"


);




socketWatch.Listen(10);




//新建线程,去接收客户端发来的信息




Thread td =


new


Thread(AcceptMgs);




td.IsBackground =


true


;




td.Start(socketWatch);




}




catch




{





}




}




/// <summary>




/// 接收客户端发送的信息




/// </summary>




/// <param name="o"></param>




private


void


AcceptMgs(


object


o)




{




try




{




Socket socketWatc = (Socket)o;




while


(


true


)




{




负责跟客户端通信的Socket




Socket socketSend = socketWatc.Accept();




//将远程连接的客户端的IP地址和Socket存入集合中




dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend);




//将远程连接的客户端的IP地址和端口号存储下拉框中




cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString());




ShowMsg(socketSend.RemoteEndPoint.ToString() +


": 连接成功"


);




//新建线程循环接收客户端发来的信息




Thread td =


new


Thread(Recive);




td.IsBackground =


true


;




td.Start(socketSend);




}




}




catch


{ }





}




/// <summary>




/// 接收客户端发来的数据,并显示出来




/// </summary>




private


void


Recive(


object


o)




{




Socket socketSend = (Socket)o;




try




{




while


(


true


)




{




//客户端连接成功后,服务器应该接受客户端发来的消息





if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




continue


;




}




byte


[] buffer =


new


byte


[1024 * 1024 * 2];




//实际接受到的有效字节数




int


r = socketSend.Receive(buffer);




//如果客户端关闭,发送的数据就为空,然后就跳出循环




if


(r == 0)




{




break


;




}




if


(buffer[0] == 0)


//如果接收的字节数组的第一个字节是0,说明接收的字符串信息




{




string


strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1);




ShowMsg(socketSend.RemoteEndPoint.ToString() +


": "


+ strMsg);




}




else


if


(buffer[0] == 1)


//如果接收的字节数组的第一个字节是1,说明接收的是文件




{




string


filePath =


""


;




SaveFileDialog sfd =


new


SaveFileDialog();




sfd.Title =


"保存文件"


;




sfd.InitialDirectory =


@"C:\Users\Administrator\Desktop"


;




sfd.Filter =


"文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"


;




//如果没有选择保存文件路径就一直打开保存框




while


(


true


)




{




sfd.ShowDialog(


this


);




filePath = sfd.FileName;




if


(


string


.IsNullOrEmpty(filePath))




{




continue


;




}




else




{




break


;




}




}




//保存接收的文件




using


(FileStream fsWrite =


new


FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))




{




fsWrite.Write(buffer, 1, r - 1);




}




ShowMsg(socketSend.RemoteEndPoint +


": 接收文件成功"


);





}




else


if


(buffer[0] == 2)


//如果接收的字节数组的第一个字节是2,说明接收的是震动




{




ZD();




}




}




}




catch


{}




}




/// <summary>




/// 显示信息




/// </summary>




/// <param name="message"></param>




private


void


ShowMsg(


string


message)




{




txtLog.AppendText(message +


"\r\n"


);




}




/// <summary>




/// 发送信息




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSend_Click(


object


sender, EventArgs e)




{





//获得选中客户端ip对应的通信Socket




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




string


strSend=txtMsg.Text;




try




{




byte


[] buffer = Encoding.UTF8.GetBytes(strSend);




//获得发送的信息时候,在数组前面加上一个字节 0




List<


byte


> list =


new


List<


byte


>();




list.Add(0);




list.AddRange(buffer);




//将泛型集合转换为数组




byte


[] newBuffer = list.ToArray();




//将了标识字符的字节数组传递给客户端




socketSend.Send(newBuffer);




txtMsg.Text =


""


;




}




catch




{




}




}




/// <summary>




/// 选择文件




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSelect_Click(


object


sender, EventArgs e)




{




//打开文件




OpenFileDialog ofd =


new


OpenFileDialog();




ofd.Title =


"选择要传的文件"


;




ofd.InitialDirectory =


@"C:\Users\Administrator\Desktop"


;




ofd.Filter =


"文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"


;




ofd.ShowDialog();




//得到选择文件的路径




txtPath.Text = ofd.FileName;




}




/// <summary>




/// 发送文件




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSendFile_Click(


object


sender, EventArgs e)




{




//判断是否选择了要发送的客户端




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




string


filePath = txtPath.Text;




if


(


string


.IsNullOrEmpty(filePath))




{




MessageBox.Show(


"请选择文件"


);




return


;




}




Thread td =


new


Thread(SendBigFile);




td.IsBackground =


true


;




td.Start();





}




/// <summary>




/// 大文件断点传送




/// </summary>




private


void


SendBigFile()




{




string


filePath = txtPath.Text;




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




try




{




//读取选择的文件




using


(FileStream fsRead =


new


FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))




{




//1. 第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件




long


length = fsRead.Length;




byte


[] byteLength = Encoding.UTF8.GetBytes(length.ToString());




//获得发送的信息时候,在数组前面加上一个字节 1




List<


byte


> list =


new


List<


byte


>();




list.Add(1);




list.AddRange(byteLength);




socketSend.Send(list.ToArray());


//




//2. 第二步:每次发送一个1MB的包,如果文件较大,则会拆分为多个包




byte


[] buffer =


new


byte


[1024 * 1024];




long


send = 0;


//发送的字节数




while


(


true


)


//大文件断点多次传输




{




int


r = fsRead.Read(buffer, 0, buffer.Length);




if


(r == 0)




{




break


;




}




socketSend.Send(buffer, 0, r, SocketFlags.None);




send += r;




ShowMsg(


string


.Format(


"{0}: 已发送:{1}/{2}"


, socketSend.RemoteEndPoint, send, length));




}




ShowMsg(


"发送完成"


);




txtPath.Text =


""


;




}




}




catch




{




}




}




/// <summary>




/// 震动




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnZD_Click(


object


sender, EventArgs e)




{




//判断是否选择了要发送的客户端




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




try




{




// 首字节是2说明是震动




byte


[] buffer =


new


byte


[1];




buffer[0] = 2;




socketSend.Send(buffer);




}




catch




{





}





}




/// <summary>




/// 震动




/// </summary>




private


void


ZD()




{




//获取当前窗体的坐标




Point point =


this


.Location;




//反复给窗体坐标复制一百次,达到震动的效果




for


(


int


i = 0; i < 100; i++)




{




this


.Location =


new


Point(point.X - 5, point.Y - 5);




this


.Location =


new


Point(point.X + 5, point.Y + 5);




}




this


.Location = point;




}




}


}


1)传输类型说明以及全局变量

这些说明以及全局变量,说的也比较清楚,也不累赘了。


2)Socket通信服务端具体步骤:

(这些步骤都是根据第一个图来的)


(1)建立一个Socket

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29


/// <summary>




/// 开启监听




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnStart_Click(


object


sender, EventArgs e)




{




try




{




//当点击开始监听的时候 在服务器端创建一个负责监IP地址跟端口号的Socket




Socket socketWatch =


new


Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);




//获取IP




IPAddress ip = IPAddress.Any;




//创建端口号




IPEndPoint port =


new


IPEndPoint(ip, Convert.ToInt32(txtPort.Text));




//监听




socketWatch.Bind(port);




ShowMsg(


"监听成功"


);




socketWatch.Listen(10);




//新建线程,去接收客户端发来的信息




Thread td =


new


Thread(AcceptMgs);




td.IsBackground =


true


;




td.Start(socketWatch);




}




catch




{





}




}

在开启监听按钮里,我们建立了Socket,以及监听的最大客户端数 socketWatch.Listen(10)

由于服务端会不停的去监视接收客户端发来的信息,如果把这个工作放到主线程里,程序会出现假死的现象,所以这里给他放到一个新的线程里。


(2)接收信息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27


/// <summary>




/// 接收客户端发送的信息




/// </summary>




/// <param name="o"></param>




private


void


AcceptMgs(


object


o)




{




try




{




Socket socketWatc = (Socket)o;




while


(


true


)




{




负责跟客户端通信的Socket




Socket socketSend = socketWatc.Accept();




//将远程连接的客户端的IP地址和Socket存入集合中




dicSocket.Add(socketSend.RemoteEndPoint.ToString(), socketSend);




//将远程连接的客户端的IP地址和端口号存储下拉框中




cboUsers.Items.Add(socketSend.RemoteEndPoint.ToString());




ShowMsg(socketSend.RemoteEndPoint.ToString() +


": 连接成功"


);




//新建线程循环接收客户端发来的信息




Thread td =


new


Thread(Recive);




td.IsBackground =


true


;




td.Start(socketSend);




}




}




catch


{ }





}

接收信息是会根据接收到字节数字的第一个字节来判断接收到的是什么

这个在方法Recive里进行判断

1


/// <summary>

复制代码

namespace SocketClient
{
    public partial class Form1 : Form
    {

        //说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息
        // 0:表示传递的是字符串信息
        // 1:表示传递的是文件信息
        // 2:表示的是震动

        /// <summary>
        /// 用来存放连接服务的IP地址和端口号,对应的Socket (这个为了以后的扩展用,现在暂时没用)
        /// </summary>
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        /// <summary>
        /// 存储保存文件的路径
        /// </summary>
        string  filePath = "";
        /// <summary>
        /// 负责通信的Socket
        /// </summary>
        Socket socketSend;       

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //不检测跨线程之间的空间调用
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                //创建负责通信的Socket
                socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //获取服务端的IP
                IPAddress ip = IPAddress.Parse(txtServer.Text.Trim());
                //获取服务端的端口号
                IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
                //获得要连接的远程服务器应用程序的IP地址和端口号
                socketSend.Connect(port);
                ShowMsg("连接成功");
                //新建线程,去接收客户端发来的信息
                Thread td = new Thread(AcceptMgs);
                td.IsBackground = true;
                td.Start();
            }
            catch { }
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        private void AcceptMgs()
        {           
            try
            {
                /// <summary>
                /// 存储大文件的大小
                /// </summary>
                long length = 0;
                long recive = 0; //接收的大文件总的字节数
                while (true)
                {
                    byte[] buffer = new byte[1024 * 1024];
                    int r = socketSend.Receive(buffer);
                    if (r == 0)
                    {
                        break;
                    }
                    if (length > 0)  //判断大文件是否已经保存完
                    {
                        //保存接收的文件
                        using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write))
                        {
                            fsWrite.Write(buffer, 0, r);
                            length -= r; //减去每次保存的字节数
                            ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive));
                            if (length <= 0)
                            {
                                ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功");
                            }
                            continue;
                        }                        
                    }
                    if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息
                    {
                        string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1);
                        ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg);
                    }
                    else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件
                    {
                        length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1));
                        recive = length;
                        filePath = "";
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.Title = "保存文件";
                        sfd.InitialDirectory = @"C:\Users\Administrator\Desktop";
                        sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
                        //如果没有选择保存文件路径就一直打开保存框
                        while (true)
                        {
                            sfd.ShowDialog(this);
                            filePath = sfd.FileName;
                            if (string.IsNullOrEmpty(filePath))
                            {
                                continue;
                            }
                            else
                            {
                                break;
                            }
                        }                                        
                    }
                    else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动
                    {
                        ZD();
                    }
                }
            }
            catch { }
            
           
        }


        /// <summary>
        /// 显示信息
        /// </summary>
        /// <param name="message"></param>
        private void ShowMsg(string message)
        {
            txtLog.AppendText(message + "\r\n");
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text);
                //获得发送的信息时候,在数组前面加上一个字节 0
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(buffer);
                //将泛型集合转换为数组
                byte[] newBuffer = list.ToArray();
                //将了标识字符的字节数组传递给客户端
                socketSend.Send(newBuffer);
                txtMsg.Text = "";
            }
            catch{}          
        }  

        /// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelect_Click(object sender, EventArgs e)
        {
            //打开文件
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "选择要传的文件";
            ofd.InitialDirectory = @"C:\Users\Administrator\Desktop";
            ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
            ofd.ShowDialog();
            //得到选择文件的路径
            txtPath.Text = ofd.FileName;
        }

        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSendFile_Click(object sender, EventArgs e)
        {       
            try
            {
                string filePath = txtPath.Text;
                if (string.IsNullOrEmpty(filePath))
                {
                    MessageBox.Show("请选择文件");
                    return;
                }
                //读取选择的文件
                using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    byte[] buffer = new byte[1024 * 1024 * 2];
                    int r = fsRead.Read(buffer, 0, buffer.Length);
                    //获得发送的信息时候,在数组前面加上一个字节 1
                    List<byte> list = new List<byte>();
                    list.Add(1);
                    list.AddRange(buffer);
                    byte[] newBuffer = list.ToArray();
                    //将了标识字符的字节数组传递给客户端
                    socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None);
                    txtPath.Text = "";
                }
            }
            catch{ }
        }

        /// <summary>
        /// 震动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnZD_Click(object sender, EventArgs e)
        {        
            try
            {
                // 首字节是2说明是震动
                byte[] buffer = new byte[1];
                buffer[0] = 2;
                socketSend.Send(buffer);
            }
            catch{ }
        }

        /// <summary>
        /// 震动
        /// </summary>
        private void ZD()
        {
            //获取当前窗体的坐标
            Point point = this.Location;
            //反复给窗体坐标复制一百次,达到震动的效果
            for (int i = 0; i < 100; i++)
            {
                this.Location = new Point(point.X - 5, point.Y - 5);
                this.Location = new Point(point.X + 5, point.Y + 5);
            }
            this.Location = point;
        }
    }
}

复制代码

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66


/// 接收客户端发来的数据,并显示出来


/// </summary>


private


void


Recive(


object


o)


{




Socket socketSend = (Socket)o;




try




{




while


(


true


)




{




//客户端连接成功后,服务器应该接受客户端发来的消息





if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




continue


;




}




byte


[] buffer =


new


byte


[1024 * 1024 * 2];




//实际接受到的有效字节数




int


r = socketSend.Receive(buffer);




//如果客户端关闭,发送的数据就为空,然后就跳出循环




if


(r == 0)




{




break


;




}




if


(buffer[0] == 0)


//如果接收的字节数组的第一个字节是0,说明接收的字符串信息




{




string


strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1);




ShowMsg(socketSend.RemoteEndPoint.ToString() +


": "


+ strMsg);




}




else


if


(buffer[0] == 1)


//如果接收的字节数组的第一个字节是1,说明接收的是文件




{




string


filePath =


""


;




SaveFileDialog sfd =


new


SaveFileDialog();




sfd.Title =


"保存文件"


;




sfd.InitialDirectory =


@"C:\Users\Administrator\Desktop"


;




sfd.Filter =


"文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"


;




//如果没有选择保存文件路径就一直打开保存框




while


(


true


)




{




sfd.ShowDialog(


this


);




filePath = sfd.FileName;




if


(


string


.IsNullOrEmpty(filePath))




{




continue


;




}




else




{




break


;




}




}




//保存接收的文件




using


(FileStream fsWrite =


new


FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write))




{




fsWrite.Write(buffer, 1, r - 1);




}




ShowMsg(socketSend.RemoteEndPoint +


": 接收文件成功"


);





}




else


if


(buffer[0] == 2)


//如果接收的字节数组的第一个字节是2,说明接收的是震动




{




ZD();




}




}




}




catch


{}


}


(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)


发送字符串信息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38


/// <summary>




/// 发送信息




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSend_Click(


object


sender, EventArgs e)




{





//获得选中客户端ip对应的通信Socket




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




string


strSend=txtMsg.Text;




try




{




byte


[] buffer = Encoding.UTF8.GetBytes(strSend);




//获得发送的信息时候,在数组前面加上一个字节 0




List<


byte


> list =


new


List<


byte


>();




list.Add(0);




list.AddRange(buffer);




//将泛型集合转换为数组




byte


[] newBuffer = list.ToArray();




//将了标识字符的字节数组传递给客户端




socketSend.Send(newBuffer);




txtMsg.Text =


""


;




}




catch




{




}




}


发送震动

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48


/// <summary>




/// 震动




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnZD_Click(


object


sender, EventArgs e)




{




//判断是否选择了要发送的客户端




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




try




{




// 首字节是2说明是震动




byte


[] buffer =


new


byte


[1];




buffer[0] = 2;




socketSend.Send(buffer);




}




catch




{





}





}




/// <summary>




/// 震动




/// </summary>




private


void


ZD()




{




//获取当前窗体的坐标




Point point =


this


.Location;




//反复给窗体坐标复制一百次,达到震动的效果




for


(


int


i = 0; i < 100; i++)




{




this


.Location =


new


Point(point.X - 5, point.Y - 5);




this


.Location =


new


Point(point.X + 5, point.Y + 5);




}




this


.Location = point;




}


发送文件(包含大文件)

首先要选择文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16


/// <summary>




/// 选择文件




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSelect_Click(


object


sender, EventArgs e)




{




//打开文件




OpenFileDialog ofd =


new


OpenFileDialog();




ofd.Title =


"选择要传的文件"


;




ofd.InitialDirectory =


@"C:\Users\Administrator\Desktop"


;




ofd.Filter =


"文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*"


;




ofd.ShowDialog();




//得到选择文件的路径




txtPath.Text = ofd.FileName;




}

然后在发送文件

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74


/// <summary>




/// 发送文件




/// </summary>




/// <param name="sender"></param>




/// <param name="e"></param>




private


void


btnSendFile_Click(


object


sender, EventArgs e)




{




//判断是否选择了要发送的客户端




if


(cboUsers.SelectedItem ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




if


(socketSend ==


null


)




{




MessageBox.Show(


"请选择要发送的客户端"


);




return


;




}




string


filePath = txtPath.Text;




if


(


string


.IsNullOrEmpty(filePath))




{




MessageBox.Show(


"请选择文件"


);




return


;




}




Thread td =


new


Thread(SendBigFile);




td.IsBackground =


true


;




td.Start();





}




/// <summary>




/// 大文件断点传送




/// </summary>




private


void


SendBigFile()




{




string


filePath = txtPath.Text;




Socket socketSend = dicSocket[cboUsers.SelectedItem.ToString()];




try




{




//读取选择的文件




using


(FileStream fsRead =


new


FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))




{




//1. 第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件




long


length = fsRead.Length;




byte


[] byteLength = Encoding.UTF8.GetBytes(length.ToString());




//获得发送的信息时候,在数组前面加上一个字节 1




List<


byte


> list =


new


List<


byte


>();




list.Add(1);




list.AddRange(byteLength);




socketSend.Send(list.ToArray());


//




//2. 第二步:每次发送一个4KB的包,如果文件较大,则会拆分为多个包




byte


[] buffer =


new


byte


[1024 * 1024];




long


send = 0;


//发送的字节数




while


(


true


)


//大文件断点多次传输




{




int


r = fsRead.Read(buffer, 0, buffer.Length);




if


(r == 0)




{




break


;




}




socketSend.Send(buffer, 0, r, SocketFlags.None);




send += r;




ShowMsg(


string


.Format(


"{0}: 已发送:{1}/{2}"


, socketSend.RemoteEndPoint, send, length));




}




ShowMsg(


"发送完成"


);




txtPath.Text =


""


;




}




}




catch




{




}




}

注意:(1)发送文件的时候会分两步发送 :第一步:发送一个包,表示文件的长度,让客户端知道后续要接收几个包来重新组织成一个文件

第二步:每次发送一个1MB的包,如果文件较大,则会拆分为多个包

(2)每个客户端连接服务端的啥时候,都会把客户端的ip以及端口号,放到下拉框里,想给那个客户端发信息,就选择对应的客户端


二、客户端的建立


1.客户端的项目建立以及页面布局

客户端的界面布局与服务端很像,就是把对应的开始监听换成连接,当然代码也会有所改变,后面会讲到·····


2.各功能按键的事件代码

先把整个服客户端的代码贴出来,然后我们在一一讲解

复制代码

namespace SocketClient
{
    public partial class Form1 : Form
    {

        //说明:在传递信息的时候,会在需要传递的信息前面加一个字符来标识传递的是不同的信息
        // 0:表示传递的是字符串信息
        // 1:表示传递的是文件信息
        // 2:表示的是震动

        /// <summary>
        /// 用来存放连接服务的IP地址和端口号,对应的Socket (这个为了以后的扩展用,现在暂时没用)
        /// </summary>
        Dictionary<string, Socket> dicSocket = new Dictionary<string, Socket>();

        /// <summary>
        /// 存储保存文件的路径
        /// </summary>
        string  filePath = "";
        /// <summary>
        /// 负责通信的Socket
        /// </summary>
        Socket socketSend;       

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //不检测跨线程之间的空间调用
            Control.CheckForIllegalCrossThreadCalls = false;
        }

        /// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                //创建负责通信的Socket
                socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //获取服务端的IP
                IPAddress ip = IPAddress.Parse(txtServer.Text.Trim());
                //获取服务端的端口号
                IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
                //获得要连接的远程服务器应用程序的IP地址和端口号
                socketSend.Connect(port);
                ShowMsg("连接成功");
                //新建线程,去接收客户端发来的信息
                Thread td = new Thread(AcceptMgs);
                td.IsBackground = true;
                td.Start();
            }
            catch { }
        }

        /// <summary>
        /// 接收数据
        /// </summary>
        private void AcceptMgs()
        {           
            try
            {
                /// <summary>
                /// 存储大文件的大小
                /// </summary>
                long length = 0;
                long recive = 0; //接收的大文件总的字节数
                while (true)
                {
                    byte[] buffer = new byte[1024 * 1024];
                    int r = socketSend.Receive(buffer);
                    if (r == 0)
                    {
                        break;
                    }
                    if (length > 0)  //判断大文件是否已经保存完
                    {
                        //保存接收的文件
                        using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write))
                        {
                            fsWrite.Write(buffer, 0, r);
                            length -= r; //减去每次保存的字节数
                            ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive));
                            if (length <= 0)
                            {
                                ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功");
                            }
                            continue;
                        }                        
                    }
                    if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息
                    {
                        string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1);
                        ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg);
                    }
                    else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件
                    {
                        length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1));
                        recive = length;
                        filePath = "";
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.Title = "保存文件";
                        sfd.InitialDirectory = @"C:\Users\Administrator\Desktop";
                        sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
                        //如果没有选择保存文件路径就一直打开保存框
                        while (true)
                        {
                            sfd.ShowDialog(this);
                            filePath = sfd.FileName;
                            if (string.IsNullOrEmpty(filePath))
                            {
                                continue;
                            }
                            else
                            {
                                break;
                            }
                        }                                        
                    }
                    else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动
                    {
                        ZD();
                    }
                }
            }
            catch { }
            
           
        }


        /// <summary>
        /// 显示信息
        /// </summary>
        /// <param name="message"></param>
        private void ShowMsg(string message)
        {
            txtLog.AppendText(message + "\r\n");
        }

        /// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text);
                //获得发送的信息时候,在数组前面加上一个字节 0
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(buffer);
                //将泛型集合转换为数组
                byte[] newBuffer = list.ToArray();
                //将了标识字符的字节数组传递给客户端
                socketSend.Send(newBuffer);
                txtMsg.Text = "";
            }
            catch{}          
        }  

        /// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelect_Click(object sender, EventArgs e)
        {
            //打开文件
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "选择要传的文件";
            ofd.InitialDirectory = @"C:\Users\Administrator\Desktop";
            ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
            ofd.ShowDialog();
            //得到选择文件的路径
            txtPath.Text = ofd.FileName;
        }

        /// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSendFile_Click(object sender, EventArgs e)
        {       
            try
            {
                string filePath = txtPath.Text;
                if (string.IsNullOrEmpty(filePath))
                {
                    MessageBox.Show("请选择文件");
                    return;
                }
                //读取选择的文件
                using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    byte[] buffer = new byte[1024 * 1024 * 2];
                    int r = fsRead.Read(buffer, 0, buffer.Length);
                    //获得发送的信息时候,在数组前面加上一个字节 1
                    List<byte> list = new List<byte>();
                    list.Add(1);
                    list.AddRange(buffer);
                    byte[] newBuffer = list.ToArray();
                    //将了标识字符的字节数组传递给客户端
                    socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None);
                    txtPath.Text = "";
                }
            }
            catch{ }
        }

        /// <summary>
        /// 震动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnZD_Click(object sender, EventArgs e)
        {        
            try
            {
                // 首字节是2说明是震动
                byte[] buffer = new byte[1];
                buffer[0] = 2;
                socketSend.Send(buffer);
            }
            catch{ }
        }

        /// <summary>
        /// 震动
        /// </summary>
        private void ZD()
        {
            //获取当前窗体的坐标
            Point point = this.Location;
            //反复给窗体坐标复制一百次,达到震动的效果
            for (int i = 0; i < 100; i++)
            {
                this.Location = new Point(point.X - 5, point.Y - 5);
                this.Location = new Point(point.X + 5, point.Y + 5);
            }
            this.Location = point;
        }
    }
}

复制代码


1)传输类型说明以及全局变量

这些说明以及全局变量,说的也比较清楚,也不累赘了。


2)Socket通信服务端具体步骤:

(这些步骤都是根据第一个图来的)


(1)建立一个通信的Socket

复制代码

/// <summary>
        /// 建立连接
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStart_Click(object sender, EventArgs e)
        {
            try
            {
                //创建负责通信的Socket
                socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //获取服务端的IP
                IPAddress ip = IPAddress.Parse(txtServer.Text.Trim());
                //获取服务端的端口号
                IPEndPoint port = new IPEndPoint(ip, Convert.ToInt32(txtPort.Text));
                //获得要连接的远程服务器应用程序的IP地址和端口号
                socketSend.Connect(port);
                ShowMsg("连接成功");
                //新建线程,去接收客户端发来的信息
                Thread td = new Thread(AcceptMgs);
                td.IsBackground = true;
                td.Start();
            }
            catch { }
        }

复制代码

在连接按钮里,我们建立了Socket

由于客户端会不停的去监视接收服务端发来的信息,如果把这个工作放到主线程里,程序会出现假死的现象,所以这里给他放到一个新的线程里。


(2)接收信息

复制代码

/// <summary>
        /// 接收数据
        /// </summary>
        private void AcceptMgs()
        {           
            try
            {
                /// <summary>
                /// 存储大文件的大小
                /// </summary>
                long length = 0;
                long recive = 0; //接收的大文件总的字节数
                while (true)
                {
                    byte[] buffer = new byte[1024 * 1024];
                    int r = socketSend.Receive(buffer);
                    if (r == 0)
                    {
                        break;
                    }
                    if (length > 0)  //判断大文件是否已经保存完
                    {
                        //保存接收的文件
                        using (FileStream fsWrite = new FileStream(filePath, FileMode.Append, FileAccess.Write))
                        {
                            fsWrite.Write(buffer, 0, r);
                            length -= r; //减去每次保存的字节数
                            ShowMsg(string.Format("{0}: 已接收:{1}/{2}", socketSend.RemoteEndPoint, recive-length, recive));
                            if (length <= 0)
                            {
                                ShowMsg(socketSend.RemoteEndPoint + ": 接收文件成功");
                            }
                            continue;
                        }                        
                    }
                    if (buffer[0] == 0) //如果接收的字节数组的第一个字节是0,说明接收的字符串信息
                    {
                        string strMsg = Encoding.UTF8.GetString(buffer, 1, r - 1);
                        ShowMsg(socketSend.RemoteEndPoint.ToString() + ": " + strMsg);
                    }
                    else if (buffer[0] == 1) //如果接收的字节数组的第一个字节是1,说明接收的是文件
                    {
                        length = int.Parse(Encoding.UTF8.GetString(buffer,1,r-1));
                        recive = length;
                        filePath = "";
                        SaveFileDialog sfd = new SaveFileDialog();
                        sfd.Title = "保存文件";
                        sfd.InitialDirectory = @"C:\Users\Administrator\Desktop";
                        sfd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
                        //如果没有选择保存文件路径就一直打开保存框
                        while (true)
                        {
                            sfd.ShowDialog(this);
                            filePath = sfd.FileName;
                            if (string.IsNullOrEmpty(filePath))
                            {
                                continue;
                            }
                            else
                            {
                                break;
                            }
                        }                                        
                    }
                    else if (buffer[0] == 2) //如果接收的字节数组的第一个字节是2,说明接收的是震动
                    {
                        ZD();
                    }
                }
            }
            catch { }
            
           
        }

复制代码

接收信息是会根据接收到字节数字的第一个字节来判断接收到的是什么,如果接收的是个大文件,首先会接收大文件的大小,然后根据大小接收相同大小的字节数组追加保存到一个文件里去。


(3)发送数据(这里分发送字符串、文件(包含大文件)、震动)


发送字符串信息

复制代码

/// <summary>
        /// 发送数据
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSend_Click(object sender, EventArgs e)
        {
            try
            {
                byte[] buffer = Encoding.UTF8.GetBytes(txtMsg.Text);
                //获得发送的信息时候,在数组前面加上一个字节 0
                List<byte> list = new List<byte>();
                list.Add(0);
                list.AddRange(buffer);
                //将泛型集合转换为数组
                byte[] newBuffer = list.ToArray();
                //将了标识字符的字节数组传递给客户端
                socketSend.Send(newBuffer);
                txtMsg.Text = "";
            }
            catch{}          
        }

复制代码


发送震动

复制代码

/// <summary>
        /// 震动
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnZD_Click(object sender, EventArgs e)
        {        
            try
            {
                // 首字节是2说明是震动
                byte[] buffer = new byte[1];
                buffer[0] = 2;
                socketSend.Send(buffer);
            }
            catch{ }
        }

        /// <summary>
        /// 震动
        /// </summary>
        private void ZD()
        {
            //获取当前窗体的坐标
            Point point = this.Location;
            //反复给窗体坐标复制一百次,达到震动的效果
            for (int i = 0; i < 100; i++)
            {
                this.Location = new Point(point.X - 5, point.Y - 5);
                this.Location = new Point(point.X + 5, point.Y + 5);
            }
            this.Location = point;
        }

复制代码


发送文件(不包含大文件)

首先要选择文件

复制代码

/// <summary>
        /// 选择文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSelect_Click(object sender, EventArgs e)
        {
            //打开文件
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "选择要传的文件";
            ofd.InitialDirectory = @"C:\Users\Administrator\Desktop";
            ofd.Filter = "文本文件|*.txt|图片文件|*.jpg|视频文件|*.avi|所有文件|*.*";
            ofd.ShowDialog();
            //得到选择文件的路径
            txtPath.Text = ofd.FileName;
        }

复制代码

然后在发送文件

复制代码

/// <summary>
        /// 发送文件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSendFile_Click(object sender, EventArgs e)
        {       
            try
            {
                string filePath = txtPath.Text;
                if (string.IsNullOrEmpty(filePath))
                {
                    MessageBox.Show("请选择文件");
                    return;
                }
                //读取选择的文件
                using (FileStream fsRead = new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Read))
                {
                    byte[] buffer = new byte[1024 * 1024 * 2];
                    int r = fsRead.Read(buffer, 0, buffer.Length);
                    //获得发送的信息时候,在数组前面加上一个字节 1
                    List<byte> list = new List<byte>();
                    list.Add(1);
                    list.AddRange(buffer);
                    byte[] newBuffer = list.ToArray();
                    //将了标识字符的字节数组传递给客户端
                    socketSend.Send(newBuffer, 0, r + 1, SocketFlags.None);
                    txtPath.Text = "";
                }
            }
            catch{ }
        }

复制代码