TCP实现服务器与客户端的连接

实现服务器和客户端的通信,有以下需求:

服务器:

(1)服务器需要接收客户端的聊天请求,为了让客户端找到自己,必须要有一个确定的 ip地址 + 端口号 来标识自己。
(2)服务器接收到客户端发来的消息,需要给客户端回复。

客户端:

(1)实现通信,客户端手续需要连接至服务器。而连接服务器,我们需要知道服务器的 ip地址 + 端口号
(2)在通信过程中,我们要给服务器发送消息,也要接收服务器回复的消息。
(3)客户端输入"quit"时通信结束。


服务器的实现:

(1)服务器调用socket起一个端口,供客户端连接。

ServerSocket tcpServerSocket = new ServerSocket (8080);//起一个端口号

(2)利用 socket.accept() 方法等待客户端的连接,如果没有客户端连接,服务器将一直处于阻塞状态。

Socket clientSocket = tcpServerSocket.accept ();//接收客户端
InetAddress clientAddress = clientSocket.getInetAddress ();//获取到客户端的ip地址
int clientPort = clientSocket.getPort ();//获取到客户端的端口号

(3)getInputStream() 方法获取的是字节流,我们需要用 InputStreamReader() 将字节流转换为字符流。
BufferedReader从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。

//获取输入字节流(即客户发送来的字节流)
InputStream is = clientSocket.getInputStream ();
//字节流转换为字符流
InputStreamReader isReader = new InputStreamReader (is,"UTF-8");
//字符流转换为缓冲字符流
BufferedReader reader = new BufferedReader (isReader);

(4)接收客户端发送的消息,服务端回复消息给客户端。

//获取输出字符流(即回复给客户端的消息)
OutputStream os = clientSocket.getOutputStream ();
PrintStream out = new PrintStream (os,true,"UTF-8");

String line; 
//line为收到的客户端的消息
while((line = reader.readLine ()) != null ) { 
	System.out.println ("收到消息:" + line);
	System.out.print ("请回复:");
	String response = scanner.nextLine ();
	out.println (response);
}

客户端的实现:

(1)起一个客户端端口,用作之后的连接工作,即和服务器连接。
我们的服务器和客户端都是在自己的电脑上的,所以服务器的 ip 为 127.0.0.1。

Socket tcpClientSocket = new Socket ();//起一个客户端端口
byte[] ipv4 = {127,0,0,1};//跟本机交互
InetAddress serverAdress = InetAddress.getByAddress (ipv4);//获取服务器的ip地址

(2)将客户端的端口连接至服务器。

//连接服务器
SocketAddress serverSocketAddress = new InetSocketAddress (serverAdress,8080);//服务器ip+端口
tcpClientSocket.connect (serverSocketAddress);//tcp是面向连接的

(3)发送给服务器消息,接收服务器回复的消息。

	while(true){
            System.out.print ("请输入:");
            String request = scanner.nextLine ();
            //当用户输入 quit 时,退出程序
            if (request.equalsIgnoreCase ("quit")) {
                break;
            }
            //通过字节流直接写入信息
            OutputStream os = tcpClientSocket.getOutputStream ();
            PrintStream out = new PrintStream (os,true,"UTF-8");//自动刷新信息

            out.println (request);

            //通过字节流,直接读取数据
            InputStream is = tcpClientSocket.getInputStream ();//获取此端口的输入流,即服务器回复的消息
            BufferedReader reader = new BufferedReader (new InputStreamReader (is,"UTF-8"));
            String response = reader.readLine ();
            System.out.println ("收到回复:" + response);

       		}

        	tcpClientSocket.close ();
    	}

完整的程序:

服务器:

import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server {

	//服务器
	
    public static void main(String[] args) throws IOException {
        ServerSocket tcpServerSocket = new ServerSocket (8080);//起一个端口号
        Scanner scanner = new Scanner (System.in);

        while(true){
            //如果没有客户端连接上来,将阻塞
            Socket clientSocket = tcpServerSocket.accept ();//接收客户端
            InetAddress clientAddress = clientSocket.getInetAddress ();//获取到客户端的ip地址
            int clientPort = clientSocket.getPort ();//获取到客户端的端口号

            System.out.printf ("有客户端连接上来 %s :%d%n",clientAddress.getHostAddress (),clientPort);

            //获取输入字节流(即客户发送来的字节流)
            InputStream is = clientSocket.getInputStream ();
            //字节流转换为字符流
            InputStreamReader isReader = new InputStreamReader (is,"UTF-8");
            //字符流转换为缓冲字符流
            BufferedReader reader = new BufferedReader (isReader);

            //获取输出字符流(即回复给客户端的消息)
            OutputStream os = clientSocket.getOutputStream ();
            PrintStream out = new PrintStream (os,true,"UTF-8");

            String line;
            while((line = reader.readLine ()) != null ) {
                System.out.println ("收到消息:" + line);
                System.out.print ("请回复:");
                String response = scanner.nextLine ();
                out.println (response);
            }

        }

    }
}

客户端:

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Client {

    //客户端

    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner (System.in);

        Socket tcpClientSocket = new Socket ();//起一个客户端端口
        byte[] ipv4 = {127,0,0,1};//跟本机交互
        InetAddress serverAdress = InetAddress.getByAddress (ipv4);//获取服务器的ip地址

        //连接服务器
        SocketAddress serverSocketAddress = new InetSocketAddress (serverAdress,8080);//服务器ip+端口
        tcpClientSocket.connect (serverSocketAddress);
        //tcp是面向连接的

        while(true){
            System.out.print ("请输入:");
            String request = scanner.nextLine ();
            //当用户输入 quit 时,退出程序
            if (request.equalsIgnoreCase ("quit")) {
                break;
            }
            //通过字节流直接写入信息
            OutputStream os = tcpClientSocket.getOutputStream ();
            PrintStream out = new PrintStream (os,true,"UTF-8");//自动刷新信息

            out.println (request);

            //通过字节流,直接读取数据
            InputStream is = tcpClientSocket.getInputStream ();//获取此端口的输入流,即服务器回复的消息
            BufferedReader reader = new BufferedReader (new InputStreamReader (is,"UTF-8"));
            String response = reader.readLine ();
            System.out.println ("收到回复:" + response);

        }

        tcpClientSocket.close ();
    }

}


注意:

ip标识一台设备,port标识一个进程
ip+port标识网络上的唯一一台设备的唯一进程

所以我们能根据 ip和端口号 对服务器发送请求。

以上程序中,我们用了一些特殊的类和方法,是实现服务器和接口的重要保障,我们依次看一看。

(1)Socket类

  • Socket,是网络编程接口,也称为套接字,是用来进行网络通信的。两者的通信通过ip地址,端口以及协议识别连接的客户端
  • 客户端与服务器进行一次网络通信,肯定要建立连接。那么在连接建立时,在客户端与服务器端都会产生一个socket实例。客户端和服务器可以通过该实例进行信息的发送与接收。

(2)ServerSocket类

  • ServerSocket类表示服务器socket,为接收连接的服务器
  • 当远程主机上的一个客户端尝试连接这个端口时,服务器就被唤醒。协商建立客户端和服务器之间的连接,并返回一个常规的Socket对象。表示两台主机之间的Socket。
  • 换句话说,服务器socket等待连接,而客户端socke发起连接。一旦ServerSocket建立了连接,服务器会使用一个常规的Socket对象向客户端发送数据。

反观实现服务器中的代码:

ServerSocket tcpServerSocket = new ServerSocket (8080);//起一个端口号
Socket clientSocket = tcpServerSocket.accept ();//接收客户端连接,并返回一个两台主机之间的socket

(3)ServerSocket 类中的 accept( ) 方法

  • public Socket accept( ) throws IOException
  • 侦听要连接到此套接字并接受它。 该方法将阻塞直到建立连接。
  • 服务器打开后,accept方法将一直等待客户端的连接,如果客户端一直没有连接,整个线程将会阻塞在此处。

(4)InputStream、InputStreamReader、BufferedReader

我们知道 InputStream 是获取字节流,但是字节流数据在读的时候不是很方便,所以我们将字节流用 InputStreamReader 类包装成字符流。而当这个数据流很大时,我们将他读入一个 BufferedReader 中,可以以行的形式读取。

  • public class InputStreamReader extends Reader
  • InputStreamReader是从字节流到字符流的桥:它读取字节,并使用指定的charset将其解码为字符 。
  • public class BufferedReader extends Reader
  • 从字符输入流读取文本,缓冲字符,以提供字符,数组和行的高效读取。 可以指定缓冲区大小,或者可以使用默认大小。

实现客户端的代码中也有体现

//获取输入字节流(即客户发送来的字节流)
InputStream is = clientSocket.getInputStream ();
//字节流转换为字符流
InputStreamReader isReader = new InputStreamReader (is,"UTF-8");
//字符流转换为缓冲字符流
BufferedReader reader = new BufferedReader (isReader);

小结:
写完这个服务器,我们肯定发现一个很大的问题,就是服务器没有客户端连接的时候它将一直阻塞。有客户端连接时也只能等到当前线程结束,要解决这一问题,我们就必须给服务器改造成线程池的版本了,那么,请关注之后的博客噢?


版权声明:本文为qq_43632625原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。