什么是IP协议?IP协议是用于报文交换网络的一种面向数据的协议,它定义了寻址方法和数据报的封装格式,任务是根据主机和目的主机的地址传送数据。
什么是TCP协议?TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于IP的传输层协议。在发送数据之前,必须与对方建立可靠的连接。一个TCP连接必须经过三次“会话”才能建立起来,也就是“三次握手”。
TCP三次握手过程:
- 主机A 通过向主机B发送一个含有同步序列号(SYN)的标志位的数据段给主机B、向主机B 请求建立连接,通过这个数据段,主机A 告诉主机B 两件事:我想要和你通信。
- 主机B 收到主机A 的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A 两件事:我已经收到你的请求,你可以传输数据了。
- 主机A 收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:我已收到回复,我现在要开始传输实际数据了。
这样3次握手就完成了,主机A和主机B 就可以传输数据了。
“三次握手”过程中始终没有应用层的数据交换,SYN这个标志位只有在CP建产连接时才会被置1,握手完成后SYN标志位被置0。
什么是UDP协议?UDP协议(User Datagram Protocol,用户数据报协议)是无连接、不可靠、面向事务的协议。传输数据之前,源端和终端不需要建立连接,当需要传送数据时,就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。在发送端,UDP传送数据的速度仅仅是受应用程序生成数据的速度、计算机的能力和传输带宽的限制。
TCP与UDP的区别
- TCP协议面向连接,UDP协议面向非连接;
- TCP协议传输速度慢,UDP协议传输速度快;
- TCP有丢包重传机制,UDP没有;
- TCP协议保证数据正确性,UDP协议可能丢包;
什么是Socket?Socket是应用层与TCP/IP协议族通信的中间软件抽象层,是一组把复杂的TCP/IP协议族隐藏在后面的接口,用于描述IP地址和端口,应用程序通常通过套接字向网络发出请求或者应答网络请求。对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
下面以C/S结构为基础来介绍TCP编程的实现。
客户端(Client)是指首先发起连接的程序,主要有三个步骤:
- 建立连接:在建立连接时需要指定服务器的IP地址和端口号;
- 交换数据:按照请求响应模型进行,由客户端发送一个请求数据到服务器端,服务器端反馈一个响应数据给客户端,如果客户端不发送请求,服务器端则不响应。
- 关闭连接:释放程序占用的端口、内存等系统资源。
服务器端(Server)是指被动等待连接的程序,主要有四个步骤:
- 监听端口:这个端口就是服务器端开放给客户端的端口,服务器端程序运行的本地计算机的IP地址就是服务器端程序的IP地址。
- 获得连接:这个连接包含客户端的信息,例如客户端IP地址。一般在服务器端编程中,当获得连接时,需要开启专门的线程处理该连接,每个连接都由独立的线程实现,这样服务器就能同时处理多个客户端。
- 交换数据:接收到客户端发送过来的数据,然后进行处理,再把处理结果发送给客户端。简单来说,就是先接收再发送。
- 关闭连接:释放程序占用的端口、内存等系统资源。
TCP服务器端事例代码:
public class Server
{
public static int port = 10000; // 服务器端的端口号
public static void main(String[] args)
{
ServerSocket serverSocket = null;
Socket socket = null;
try
{
serverSocket = new ServerSocket(port);
System.out.println("服务器已启动,等待接受消息中 ...");
while(true)// 循环等待接受客户端消息
{
socket = serverSocket.accept();// 获得连接
new HandleThread(socket);// 启动线程
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
serverSocket.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
}
/**
* 服务器端线程,同时处理多个客户端
*/
class HandleThread extends Thread
{
Socket socket = null;
InputStream is = null;
OutputStream os = null;
public HandleThread(Socket socket)
{
this.socket = socket;
start(); // 启动线程
}
public void run()
{
byte[] b = new byte[1024];
try
{
// 初始化流
os = socket.getOutputStream();
is = socket.getInputStream();
int n = is.read(b);// 读取数据
String answer = "Hello, I'm server!";
os.write(answer.getBytes());// 反馈数据
System.out.println("客户端发送内容为(" + new SimpleDateFormat("hh:mm:ss").format(new Date())
+ "):" + new String(b, 0, n));// 输出
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
close();
}
}
/**
* 关闭流和连接
*/
private void close()
{
try
{
os.close();
is.close();
socket.close();
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
ServerSocket的accept方法是阻塞式的,它会等待客户端与之建立连接。Socket的getInputStream方式也是阻塞的,如果从输入流中没有读取到数据程序会一直在那里不动,直到客户端往Socket的输出流中写入了数据,或关闭了Socket的输出流,对于客户端的Socket也是同样如此。
TCP客户端代码:
public class Client
{
public static String serverIP = "127.0.0.1"; // 服务器端IP地址
public static int port = 10000; // 服务器端端口号
public static void main(String[] args)
{
Socket socket = null;
InputStream is = null;
OutputStream os = null;
// 发送内容
String data = "Hello, I'm client.";
try
{
socket = new Socket(serverIP, port);// 建立连接
os = socket.getOutputStream();// 初始化流
is = socket.getInputStream();
byte[] b = new byte[1024];
os.write(data.getBytes());// 发送数据
int n = is.read(b);// 接收数据
// 输出反馈数据
System.out.println("服务器反馈(" + new SimpleDateFormat("hh:mm:ss").format(new Date()) + "):"
+ new String(b, 0, n));
}
catch(Exception e)
{
e.printStackTrace(); // 打印异常信息
}
finally
{
try
{
// 关闭流和连接
is.close();
os.close();
socket.close();
}
catch(Exception e2)
{
e2.printStackTrace();
}
}
}
}
运行服务器端程序,接着运行多次客户端程序,服务器端得到的消息是:
服务器已启动,等待接受消息中 ...
客户端发送内容为(09:10:12):Hello, I'm client.
客户端发送内容为(09:10:14):Hello, I'm client.
客户端发送内容为(09:10:18):Hello, I'm client.
客户端发送内容为(09:10:19):Hello, I'm client.
客户端发送内容为(09:10:20):Hello, I'm client.
UDP方式由于不需要建立专用的连接,对服务器的压力要比TCP小很多,但是同时传输不可靠,有可能发生数据丢失的情况。在Java API中,实现UDP方式主要由两个类实现:DatagramSocket和DatagramPacket。前者用于发送和接收数据,后者用于数据的封装(IP地址、端口号和数据内容)。
UDP服务器端代码:
public class UDPServer
{
public static int port = 10000; // 服务器端的端口号
public static void main(String[] args)
{
testUDPServer();
}
public static void testUDPServer()
{
DatagramSocket ds = null; // 连接对象
DatagramPacket receiveDp; // 接收数据包对象
final int PORT = 10000; // 端口
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
try
{
// 建立连接,监听端口
ds = new DatagramSocket(PORT);
System.out.println("服务器端已启动:");
while(true)
{
ds.receive(receiveDp);// 接收
new HandleThread(ds, receiveDp);// 启动线程处理数据包
}
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
ds.close();
}
catch(Exception e)
{}
}
}
}
/**
* 逻辑处理线程
*/
class HandleThread extends Thread
{
/** 连接对象 */
DatagramSocket ds = null;
/** 接收到的数据包 */
DatagramPacket dp = null;
public HandleThread(DatagramSocket ds, DatagramPacket dp)
{
this.ds = ds;
this.dp = dp;
start(); // 启动线程
}
public void run()
{
try
{
byte[] data = dp.getData();// 获得缓冲数组
int len = dp.getLength();// 获得有效数据长度
InetAddress clientAddress = dp.getAddress();// 客户端IP
int clientPort = dp.getPort();// 客户端端口
// 输出
System.out.println("客户端IP:" + clientAddress.getHostAddress());
System.out.println("客户端端口号:" + clientPort);
System.out.println("客户端发送内容:" + new String(data, 0, len));
// 反馈到客户端
byte[] b = new SimpleDateFormat("hh:mm:ss").format(new Date()).getBytes();
DatagramPacket sendDp = new DatagramPacket(b, b.length, clientAddress, clientPort);
ds.send(sendDp);// 发送
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
和TCP方式的网络编程类似,服务器端的receive方法也是阻塞方法,如果客户端不发送数据,则程序会在该方法处阻塞。当客户端发送数据到达服务器端时,则接收客户端发送过来的数据,然后将客户端发送的数据内容读取出来进行处理。从客户端发送过来的数据包中可以读取出客户端的IP以及客户端端口号。最后关闭服务器端连接,释放占用的系统资源。
UDP客户端代码:
public class UDPClient
{
public static String serverIP = "127.0.0.1"; // 服务器端IP地址
public static int port = 10000; // 服务器端端口号
public static void main(String[] args)
{
DatagramSocket ds = null; // 连接对象
DatagramPacket sendDp; // 发送数据包对象
DatagramPacket receiveDp; // 接收数据包对象
try
{
ds = new DatagramSocket();
InetAddress address = InetAddress.getByName(serverIP);
byte[] b = new byte[1024];
receiveDp = new DatagramPacket(b, b.length);
String content = new SimpleDateFormat("hh:mm:ss").format(new Date());
byte[] data = content.getBytes();
sendDp = new DatagramPacket(data, data.length, address, port);// 初始化发送包对象
ds.send(sendDp);// 发送
ds.receive(receiveDp);// 接收
byte[] response = receiveDp.getData();// 读取反馈内容,并输出
int len = receiveDp.getLength();
String s = new String(response, 0, len);
System.out.println("服务器端回答为:" + s);
}
catch(Exception e)
{
e.printStackTrace();
}
finally
{
try
{
ds.close();
}
catch(Exception e)
{
}
}
}
}