JAVA网络编程——TCP

tcpJAVA

  • TCP协议介绍
  • JAVA TCP编程
    • 步骤
    • 组件
      • ServerSocket
      • Socket
    • 状态
    • 实例
      • Client
      • Server(easy)
      • Server

TCP协议介绍

TCP: Transmission Control Protocol

  1. 传输控制协议,面向连接的协议
  2. 两台机器的可靠无差错的数据传输
  3. 双向字节流传递

JAVA TCP编程

步骤

TCP协议:有链接/保证可靠的无误差通讯

  1. 服务器:创建一个ServerSocket,等待连接
  2. 客户机:创建一个Socket,连接到服务器
  3. 服务器:ServerSocket接收到连接,创建一个Socket和客户的Socket建立专线连接,后续服务器和客户机的对话(这一对Socket)会在一个单独的线程(服务器端)上运行
  4. 服务器的ServerSocket继续等待连接,返回第1步

软件服务器的两个要求:
1 它能够实现一定的功能;
2 它能够在一个公开地址上对外提供服务

组件

ServerSocket

服务器码头:

  • 需要绑定port
  • 如果有多种网卡,需要绑定一个IP地址

Socket

运输通道:

  • 客户端需要绑定服务器的地址和Port
  • 客户端往Socket输入流写入数据,送到服务端
  • 客户端从Socket输出流取服务器端过来的数据
  • 服务端反之亦然

状态

  • 服务端等待响应时,处于阻塞状态
  • 服务端可以同时响应多个客户端
  • 服务端每接受一个客户端,就启动一个独立的线程与之对应
  • 客户端或者服务器端都可以选择关闭这对Socket的通道

实例

  • 服务端先启动,且一直保留
  • 客户端后启动,可以先退出

我们先看客户端实现

Client

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.*;

public class TcpClient {

	public static void main(String[] args) {
		try
		{
			Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8001); // 需要服务端先开启
			
			// 同一个通道,服务端的输出流就是客户端的输入流,服务端的输入流就是客户端的输出流
			InputStream ips = s.getInputStream(); //开启通道的输入流
			BufferedReader brNet = new BufferedReader(new InputStreamReader(ips));
			// BufferReader 用来包装输入流,使读取更有效率
			
			OutputStream ops = s.getOutputStream(); // 开启通道的输出流
			DataOutputStream dos = new DataOutputStream(ops);
			
			BufferedReader brKey = new BufferedReader(new InputStreamReader(System.in));
			// 键盘输入流
			while(true)
			{
				String strWord = brKey.readLine();
				if (strWord.equalsIgnoreCase("quit"))
				{
					break;
				}
				else
				{
					System.out.println("I want to send:" + strWord);
					dos.writeBytes(strWord + System.getProperty("line.separator"));
					
					System.out.println("Server said: " + brNet.readLine());
				}
			}
			
			dos.close();
			brNet.close();
			brKey.close();
			s.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}

}

dos.writeBytes语句就是把一段语句写入输出流中,发送给服务器。socket定向连接到127.0.0.1:8001, 是因为服务器的ServerSocket被设置在8001端口。此时若服务器没有在8001端口打开Socket,将会抛出异常。连接而上Socket后,打开输入流和输出流,并对其进行包装。包装一个键盘输入流以读取键盘接收到的信息。然后进入循环输入阶段。当输入为quit时,退出输入循环并关闭输入输出流及Socket。这里需要注意关闭上层包装类会同时关闭其包装的类,因此不需要再关闭InputStreamOutputStream了。

接下来看一个简单的服务器端实现。

Server(easy)

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.*;

/**
 * 一次客户端tcp通信
 * @author Administrator
 *
 */
public class TcpServer {

	public static void main(String[] args) {
		try
		{
			ServerSocket ss = new ServerSocket(8001); // 驻守在8001端口
			Socket s = ss.accept();
			System.out.println("welcome to the java world");
			InputStream ips = s.getInputStream(); // 有人连上了,打开输入流
			OutputStream ops = s.getOutputStream(); // 打开输出流
			// 同一个通道,服务端的输出流就是客户端的输入流,服务端的输入流就是客户端的输出流
			
			ops.write("Hello, Client!".getBytes()); // 输出一句话给客户端
			
			BufferedReader br = new BufferedReader(new InputStreamReader(ips));
			// 从客户端读取一句话
			System.out.println("Client said:" + br.readLine());
			
			ips.close();
			ops.close();
			s.close();
			ss.close();
		}
		catch (IOException e)
		{
			e.printStackTrace();
		}
	}

}

这个服务器端只能进行一次通信,结束后就关闭Socket。它现在8001端口开启一个监听socket,当接收到连接请求以后,就返回一个socket,并利用这个socket进行通信。通信过程包括接收到客户端的一句话,打印出来并返回客户端一句话,然后关闭输入输出流及Socket。这时客户端将会抛出异常,因为socket非正常关闭了。
上面这种server有两个问题:

  1. 只能进行一次通信
  2. 只能同时处理一个client的访问请求
    而真正的服务器肯定不是这样的。接下来我们来看一个功能更为齐全的Server

Server

import java.net.*;
import java.io.*;

public class TcpServer2 {

	public static void main(String[] args) {
		try
		{
			ServerSocket ss = new ServerSocket(8001);
			while(true)
			{
				Socket s = ss.accept();
				System.out.println("来了一个Client");
				new Thread(new Worker(s)).start();
			}
			// ss.close();
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}

}
import java.net.*;
import java.io.*;

public class Worker implements Runnable {

	Socket s;
	
	public Worker(Socket s)
	{
		this.s = s;
	}
	
	@Override
	public void run() {
		try {
			System.out.println("服务人员已经启动");
			InputStream ips = s.getInputStream();
			OutputStream ops = s.getOutputStream();
			
			BufferedReader br = new BufferedReader(new InputStreamReader(ips));
			DataOutputStream dos = new DataOutputStream(ops);
			while(true)
			{
				String strWord = br.readLine();
				System.out.println("Client said:" + strWord + ":" + strWord.length());
				if (strWord.equalsIgnoreCase("quit"))
					break;
				String strEcho = strWord + "666";
				// dos.writeBytes(strWord + "---->" + strEcho + "\r\n");
				System.out.println("server said:" + strWord + "---->" + strEcho);
				dos.writeBytes(strWord + "---->" + strEcho + System.getProperty("line.separator"));
			}
			br.close();
			// 关闭包装类,会自动关闭包装类中所包装的底层类,所以不需要在调用ips.close()
			dos.close();
			s.close();
		} catch (Exception e)
		{
			e.printStackTrace();
		}

	}

}

在服务器主线程里,服务器在8001端口部署ServerSocket进行监听,当监听到一个客户端连接的请求,就会开启一个socket及一个新线程,用来专门处理与这个客户端的信息交互。在这个交互线程中,服务器会循环的接收客户端传来的信息,并且返回给客户端一个信息。由于我们设置的是客户端单方面断开连接的方式,所以当客户端断开连接后,服务器端将抛出一个异常。

以上的服务器端实现并不完整,包括断开连接的处理,以及如何改善线程数过多的并发问题。不过作为一个简单的Demo,可以从中了解到JAVA tcp编程的诸多细节。

你可能感兴趣的:(Java)