TCP 提供面向连接的服务。TCP 不提供广播或多播服务。由于 TCP 要提供可靠的、面向连接的运输服务,因此不可避免地增加了许多的开销。这不仅使协议数据单元的首部增大很多,还要占用许多的处理机资源。
网络上两个程序通过双向通信实现数据交换,socket又叫套接字,每个应用程序开启后,都会在传输层端口上绑定一个socket,不同应用程序之间通过寻找端口找到socket实现数据通信。
Socket连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。
建立连接
TCP协议提供可靠的面向连接服务,采用三次握手建立连接。
第一次握手:建立连接时,客户端发送SYN包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到SYN包,向客户端返回ACK(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RCVD状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据,也就是ESTABLISHED状态。
终止连接
采用四次挥手断开双向连接。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1。
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
using System.Collections.Generic;
namespace TCP
{
class Program
{
static void Main(string[] args)
{
Server server = new Server();
server.SterUp();
//服务器信息发送 补充;
while (true)
{
string str = Console.ReadLine();
server.SendAll(str);
}
}
}
public class Server
{
//配置相关;
private string _ip = "192.168.30.18";
//1024,65535 端口范围;
private int _port = 10000;
//服务器套接字;
private Socket _server;
//接受客户端连接的线程,因为Accept是一个阻赛线程的方法,而且此方法还需要循环执行;
// accept Client Connect Thread 接受 客户端 连接 线程
private Thread _acceptClientConnectThread;
//所有已经连接的客户端;
private List _clientList =new List();
///
/// 启动服务器=建立流式套接字+配置本地地址;
///
public void SterUp()
{
try
{
//固定格式 寻址方案 套接字类型 协议类型
_server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//配置本地地址中 向量转换;
EndPoint endPoint = new IPEndPoint(IPAddress.Parse(_ip), _port);
//配置本地地址;
_server.Bind(endPoint);
//监听和接受客户端请求的数量;
_server.Listen(30);
////接受客户端连接 运行时需要防火墙许可;
//_server.Accept();
//单线程无法继续进行,引入多线程;
//Console.WriteLine("执行到这里");
_acceptClientConnectThread = new Thread(AcceptClientConnect);
_acceptClientConnectThread.Start();
////服务器的前四步检测;
//Console.WriteLine("{0}:{1} StateUp...",_ip,_port);
////服务器的前四步检测;
}
catch (Exception e)
{
Console.WriteLine( e.Message);
}
}
///
/// Accept Client Connect接受 客户端 连接;
///
public void AcceptClientConnect()
{
while (true)
{
try
{
//接受客户端连接,接一次执行一次, 返回值为客户端的套接字;
Socket clientSocket = _server.Accept();
//维护一个客户端列表;
_clientList.Add(clientSocket);
//获取客户端的网络地址标识 向上转型 ;
IPEndPoint clientEndPoint = clientSocket.RemoteEndPoint as IPEndPoint;
// 输出 地址 端口
Console.WriteLine("{0}:{1} Connent....", clientEndPoint.Address.ToString(), clientEndPoint.Port);
//服务器的前四步检测;
//接受客户端消息的线程;
Thread acceptClientMsg = new Thread(AcceptMsg);
acceptClientMsg.Start(clientSocket);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
///
/// 接收消息;
///
public void AcceptMsg(object obj)
{
// 强转为Socket类型 向下转型;
Socket client = obj as Socket;
//字节数组 接受传来消息(Receive) 默认的储存缓冲区的大小 64K的大小 1024位1K 等于65534=1024*64 (ReceiveBufferSize);
byte[] buffer = new byte[client.ReceiveBufferSize];
//获取客户端的网络地址标识 向上转型 ;
IPEndPoint clientEndPoint = client.RemoteEndPoint as IPEndPoint;
try
{
//循环接收消息;
while (true)
{
//接收消息(的长度);
int len = client.Receive(buffer);
//字符串与字符数组相互转换using System.Text;
string str = Encoding.UTF8.GetString(buffer, 0, len);
//输出 地址 端口 消息
Console.WriteLine("Receive {0}:{1}:{2}", clientEndPoint.Address.ToString(), _port, str);
}
}
//套接字异常
catch (SocketException e)
{
Console.WriteLine(e.Message);
_clientList.Remove(client);
}
}
///
/// 发送给某一人;
///
///
///
public void Send(string str ,Socket client)
{
try
{
//string=>byte[]
byte[] strBytes = Encoding.UTF8.GetBytes(str);
client.Send(strBytes);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
///
/// 发送给所有人信息 添加命名空间和字典 using System.Collections.Generic;
///
public void SendAll(string str)
{
for (int i = 0; i < _clientList.Count; i++)
{
Send(str,_clientList[i]);
}
}
//最后关闭套接字;
public void Cloce()
{
//关闭客户端套接字相关;
if (_clientList.Count>0)
{
for (int i = 0; i < _clientList.Count; i++)
{
_clientList[i].Close();
}
}
//列表清除;
_clientList.Clear();
//关闭服务器套接字相关;
_server.Close();
//监听线程关闭;
_acceptClientConnectThread.Abort();
}
}
}
using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Text;
namespace TCP_Client
{
class Program
{
static void Main(string[] args)
{
Client client = new Client();
client.StartUp();
//发送+测试;
//这是要注意关于网络我们会有很多不可控制的错,所以要进行抓错!;
while (true)
{
string str = Console.ReadLine();
client.Send(str);
}
}
}
public class Client
{
//套接字;
private Socket _client;
//提取出来;
private Thread _acceptServerMsg;
public void StartUp()
{
try
{
_client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//输对方的IP地址 客户端连接;
_client.Connect("192.168.30.18", 10000);
//F5测试;
//客户端的前四步检测;
//补充客户端 接受消息的线程;
_acceptServerMsg = new Thread(AcceptServerMsg);
_acceptServerMsg.Start();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
//模仿Server 添加完就是双向输入输出;
public void AcceptServerMsg()
{
byte[] buffer = new byte[1024 * 64];
while (true)
{
try
{
int len = _client.Receive(buffer);
string str = Encoding.UTF8.GetString(buffer, 0, len);
Console.WriteLine("Reveive Msg Form Server:{0}", str);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
}
///
/// 发送消息;
///
///
public void Send(string str)
{
try
{
//引入using System.Text;
//将字符串转换为数组;
byte[] strBytes = Encoding.UTF8.GetBytes(str);
_client.Send(strBytes);
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
///
/// 客户端 套接字关闭;
///
public void Close()
{
//连接关闭;
if (_client.Connected)
{
_client.Close();
}
//线程关闭;
_acceptServerMsg.Abort();
}
}
}