Java学习日记——从Socket类到多线程80年代聊天室

关键字:IP,TCP,UDP,客户端,服务器端,C/S ,B/S,Echo,ServerSocket,Socket

简介

网络编程可以让多台电脑实现串联,网络编程就是服务器端与客户端编程的开发。主流网络编程有两种形式:

第一种是C/S结构,也就是客户、服务器结构,缺点是麻烦,维护不便,因为要开发两套代码,也要维护两套代码。但是好处是安全,因为使用自己的连接端口,使用自己的通讯协议。

第二种是B/S结构,只开发一套服务器端代码,客户端使用浏览器访问。好处是利于开发和维护,坏处是使用公共的HTTP协议和公共的80端口,造成我们的程序安全性不高。

在C/S结构中,程序又可以分为两类,TCP程序与UDP程序。TCP使用可靠的连接方式进行传输。UDP使用不可靠的协议,属于数据包协议。

在Java中为我们提供的类就是ServerSocket类(服务器类)和Socket类(客户端类)。服务器类主要在服务器端工作,用于接收用户请求。客户端类表示每一个连接到服务器的用户。

我们使用一个经典的程序开发模型——Echo程序来做一个简单的演示。

要求:我们要实现客户端发出一个数据,服务器端接收到之后 加上一个"Echo"进行返回。允许多次输入,不能在每次连接之后都关闭服务器端。如果输入"exit"则退出程序。

我们来看一段代码。

package com.chuchu.server ;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class EchoServer {
	public static void main(String[] args) throws Exception {
		ServerSocket server = new ServerSocket(9999) ;
		System.out.println("等待客户端连接。。。");
		Socket client = server.accept() ; // 客户端连接
		Scanner scan = new Scanner(client.getInputStream()) ;
		PrintStream out = new PrintStream(client.getOutputStream()) ;
		boolean flag = true ;
		while(flag) {
			if(scan.hasNext()) {
				String value = scan.next() ;
				if("Exit".equalsIgnoreCase(value)) 
					flag = false ;
				else 
					out.println("【ECHO】" + value);
			}
		}
		server.close();
		client.close();
		scan.close();
	}
}
package com.chuchu.client ;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class EchoClient {
	private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)) ;

	public static void main(String[] args) throws Exception {
		Socket client = new Socket("localhost", 9999) ;
		Scanner scan = new Scanner(client.getInputStream()) ;
		scan.useDelimiter("\n") ;
		PrintStream out = new PrintStream(client.getOutputStream()) ;
		boolean flag = true ;
		while(flag) {
			String input = getString("请输入要发送的内容") ;
			out.println(input);
			if(scan.hasNext()) {
				System.out.println(scan.next());
			}
			if("Exit".equalsIgnoreCase(input)) {
				flag = false ;
			}
		}
		client.close();
		scan.close();
		INPUT.close();
	}
	
	public static String getString(String promot) throws Exception {
		System.out.println(promot);
		String value = INPUT.readLine() ;
		return value ;
	}
}

在上面的代码中我们首先使用了Java提供的两个类实例化了服务器端和客户端,在服务器端我们使用ServerSocket对象中的accept()方法连接到客户端,获取Socket对象,然后再利用Socket对象中的getInputStream()方法和getOutputStream()方法获取输入输出流。在这里我们可以使用PrintStream类帮助我们在每次获取完成数据之后进行刷新操作。

客户端类大同小异,只不过我们可以将输入数据的代码封装成一个方法,并将键盘输入流封装成一个常量。

我们来看一下运行结果:
在这里插入图片描述
在这里插入图片描述
通过运行结果我们看到,服务器端成功接收到数据并将其处理后返回给了客户端。一个简单的Echo模型就算是完成了。但是当我们尝试再次启动一个客户端的时候,问题就出现了。

单线程Echo程序带来的问题

在这里插入图片描述
我们发现报错,Exception in thread “main” java.net.ConnectException: Connection refused: connect
这是由于我们的这个程序是单线程的,要向被多个用户访问我们需要将其改造成多线程。服务器端的每一个线程都为一个客户端实现Echo服务支持。

对刚才代码的改进

package com.chuchu.server;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

import com.sun.security.ntlm.Client;

public class EchoServer {
	private static class ClientThread implements Runnable {
		private Socket client = null;
		private Scanner scan = null;
		private PrintStream out = null;
		private boolean flag = true;

		public ClientThread(Socket client) throws Exception {
			this.client = client;
			scan = new Scanner(client.getInputStream());
			scan.useDelimiter("\n");
			out = new PrintStream(client.getOutputStream());
		}

		@Override
		public void run() {
			while (flag) {
				if (scan.hasNext()) {
					String value = scan.next();
					if ("exit".equalsIgnoreCase(value)) {
						out.println("欢迎下次使用");
						flag = false;
					} else {
						out.println("【Echo】" + value);
					}
				}
			}
			try {
				scan.close();
				out.close();
				client.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws Exception {
		ServerSocket server = new ServerSocket(9999);
		System.out.println("等待客户端连接。。。");
		boolean flag = true;
		while (flag) {
			Socket client = server.accept();
			new Thread(new ClientThread(client)).start();
		}
		server.close();
	}
}
package com.chuchu.client;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class EchoClient {
	private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in));

	public static void main(String[] args) throws Exception {
		Socket client = new Socket("localhost", 9999);
		Scanner scan = new Scanner(client.getInputStream());
		scan.useDelimiter("\n");
		PrintStream out = new PrintStream(client.getOutputStream());
		boolean flag = true;
		while (flag) {
			String input = getString("请输入要发送的内容");
			out.println(input);
			if (scan.hasNext()) {
				System.out.println(scan.next());
			}
			if ("Exit".equalsIgnoreCase(input)) {
				flag = false;
			}
		}
		client.close();
		scan.close();
		INPUT.close();
	}

	public static String getString(String promot) throws Exception {
		System.out.println(promot);
		String value = INPUT.readLine();
		return value;
	}
}

在上面代码中我们实现了一个内部类,这个类实现Runnable接口,构造方法里面为客户对象赋值,获取输入输出流。在run()方法中我们执行所有业务操作。

而在main()方法中我们写一个while循化接收客户端对象,没接收到一个就为此对象单开一个线程,为其服务。

最后不要忘记关闭资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值