1 双向通讯—创建服务端
(双向通信是指通信双方中,任何一方都可为发送端,任何一方都可为接收端)
(1 创建ServerSocket对象,accept()返回socket)
(2 双向通讯——>也要创建键盘输入对象)
(3 通过与客户端对应的Socket对象获取输入流对象)
(4 通过与客户单对应的Socket对象获取输出流对象)
(5 while(true)里面读取消息和发送消息——>顺序不能乱)
(但是顺序永远先是——>服务端读取客户端发送的消息——>然后在向客户端发送消息)
public class TwoWaySocketServer {
public static void main(String[] args) {
System.out.println("服务端启动!监听端口8888。。。。");
try(ServerSocket serverSocket = new ServerSocket(8888);
Socket socket = serverSocket.accept();
//创建键盘输入对象
Scanner scanner = new Scanner(System.in);
//通过与客户端对应的Socket对象获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//通过与客户单对应的Socket对象获取输出流对象
PrintWriter pw = new PrintWriter(socket.getOutputStream());){
while(true){
//读取客户端发送的消息
String str = br.readLine();
System.out.println("客户端说:"+str);
String keyInput = scanner.nextLine();
//发送到客户端
pw.println(keyInput);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
2 双向通讯—创建客户端
(1 创建Socket对象,传递IP和端口号)
(2 创建键盘输入对象)
(3 通过与服务端对应的Socket对象获取输入流对象——>读服务端发送过来的消息)
(4 通过与服务端对应的Socket对象获取输出流对象——>给服务端发送消息)
(5 while(true)里面发信息和读取消息——>顺序不能乱)
(但是现在有一个问题,就是接收消息和发送消息都在同一个线程当中,那么同一个线程的代码在执行时,完全是一个串行化的执行过程,所以他就没有办法让一个人说两句,只能你说一句我说一句——>如何能实现二者之间不受制约的相互的想说几句就说几句呢——>就要使用多线程了——>点对点通讯)
public class TwoWaySocketClient {
public static void main(String[] args) {
try(Socket socket = new Socket("127.0.0.1", 8888);
//创建键盘输入对象
Scanner scanner = new Scanner(System.in);
//通过与服务端对应的Socket对象获取输入流对象
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//通过与服务端对应的Socket对象获取输出流对象
PrintWriter pw = new PrintWriter(socket.getOutputStream());){
while (true) {
String keyInput = scanner.nextLine();
pw.println(keyInput);
pw.flush();
String input = br.readLine();
System.out.println("服务端说:" + input);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
3 点对点通讯—创建客户端
(之前对于发送消息和接收消息都放在一个线程里来实现的会冲突——>这次放在两个线程里面——>相当于点对点的聊天)
(1 主线程启动服务端——>加上发送消息的线程——>加上接受消息的线程)
(2 主线程里——>socket对象不能放在try里面了,主线程结束会把对象释放,socket对象没有就相当于与客户端断开连接了——>所以要放在try外面——>无论是接收还是发送消息都是通过这个socket对象获得输入和输出流的——>再吧socket对象分别传入2个线程之中)
(3 发送消息的线程——>主线程传递进来的socket——>用构造方法传进来——>重写run方法,把方法写外面,run里面调用——>创建键盘输入和向客户端输出消息的输出流——>while(true)多次发送消息——>先从键盘获取,再通过输出流输出——>在主线程里——>将与客户端对应的Socket对象传递给发送消息的线程,并启动该线程)
(4 接收消息的线程——>和发送消息的线程一样——>构造方法传进来socket——>重写run——>只需要创建缓冲字符流转换流读取客户端的消息——>while(true)多次读——>在主线程里——>将与客户端对应的Socket对象传递给接收消息的线程,并启动该线程)
(5 主线程里面的ServerSocket 是可以关闭掉的,因为一旦建立连接他们是平等的他们都是socket——>二者通讯的Socket别关闭就可以了)
/**
* 发送消息线程----------------------------------------------------------------------------
*/
class Send extends Thread{
private Socket socket; //主线程传递进来的socket
public Send(Socket socket){ //用构造方法传进来
this.socket = socket;
}
@Override //重写run方法,把方法写外面,run里面调用
public void run() {
this.sendMsg();
}
/**
* 发送消息
*/
private void sendMsg(){ //创建键盘输入和向客户端输出消息的输出流
//创建Scanner对象
try(Scanner scanner = new Scanner(System.in);
//创建向对方输出消息的流对象
PrintWriter pw = new PrintWriter(this.socket.getOutputStream());){
while(true){ //多次发送消息
String msg = scanner.nextLine();//先从键盘获取
pw.println(msg); //再通过输出流输出
pw.flush(); //刷新
}
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 接收消息的线程---------------------------------------------------------------------------
*/
class Receive extends Thread{
private Socket socket;
public Receive(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.receiveMsg();
}
/**
* 用于接收对方消息的方法
*/
private void receiveMsg(){
//创建用于接收对方发送消息的流对象
try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));){
while(true){
String msg = br.readLine();
System.out.println("他说:"+msg);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
//主线程----------------------------------------------------------------------------------
public class ChatSocketServer {
public static void main(String[] args) {
try(ServerSocket serverSocket = new ServerSocket(8888);){
System.out.println("服务端启动,等待连接。。。。。");
Socket socket = serverSocket.accept(); //放在外面
System.out.println("连接成功!");
new Send(socket).start();
new Receive(socket).start();
}catch(Exception e){
e.printStackTrace();
}
}
}
4 点对点通讯—创建服务器
(和创建客户端方式差不多)
(1 主线程里面创建socket对象,给定IP和端口——>要写在try外面)
(2 发送消息的线程,与服务端对应的socket对象,构造方法传递,重写run方法,创建Scanner和PrinrWriter对象,while(true)一直发送,拿到信息,打印,刷新,主线程中实例化,开启线程)
(3 接收消息的线程,与服务端对应的socket对象,构造方法传递,重写run方法,创建BufferedReader和InputStreamReader对象,while(true)一直读取,按行读,打印,主线程中实例化,开启线程)
/**
* 用于发送消息的线程类----------------------------------------------------------------------
*/
class ClientSend extends Thread{
private Socket socket;
public ClientSend(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.sendMsg();
}
/**
* 发送消息
*/
private void sendMsg(){
//创建Scanner对象
try(Scanner scanner = new Scanner(System.in);
//创建向对方输出消息的流对象
PrintWriter pw = new PrintWriter(this.socket.getOutputStream());){
while(true){
String msg = scanner.nextLine();
pw.println(msg);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 用于接收消息的线程类----------------------------------------------------------------------
*/
class ClientReceive extends Thread{
private Socket socket;
public ClientReceive(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.receiveMsg();
}
/**
* 用于接收对方消息的方法
*/
private void receiveMsg(){
//创建用于接收对方发送消息的流对象
try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));){
while(true){
String msg = br.readLine();
System.out.println("他说:"+msg);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
//主线程----------------------------------------------------------------------------------
public class ChatSocketClient {
public static void main(String[] args) {
try {
Socket socket = new Socket("127.0.0.1", 8888);
System.out.println("连接成功!");
new ClientSend(socket).start();
new ClientReceive(socket).start();
}catch(Exception e){
e.printStackTrace();
}
}
}
5 点对点通讯—优化
(在上述代码中,会发现有大量代码,相似的是极高的
——>在使用TCP协议做消息发送时,他们仅是在链接时会有客户端跟服务端的一个角色的区分,一旦链接上了,那么他们都是相同的身份的,那就没必要弄一个客户端和服务端,把二者何为一起——>只是在连接时,在程序当中区分一下谁是服务端谁是客户端)
(1 对于主线程来说区分客户端还是服务端取决于,输入的是什么,启动服务端就创建serverSocket,客户端就创建Socket,——>但就不能用这个 try-with-resources了,用finally关闭资源——>Scanner,serverSocket,socket都创建在外面——>实例化键盘输入——>根据输入选择启动什么端,用字符串数组按照逗号分隔,启动服务端和客户端)
(2 发送消息线程——>构造方法得到socket,Scanner两个参数,重写run方法,创建向对方输出消息的流对象PrintWriter——>while(true)一直发送,拿到信息,打印,刷新,主线程中实例化,开启线程)
(3 接收消息线程——>构造方法得到socket,重写run方法,创建用于接收对方发送消息的流对象BufferedReader 和InputStreamReader——>while(true)一直读取,按行读,打印,主线程中实例化,开启线程)
(现在已经可以实现了点对点的通讯,但是目前一个服务器只能与一个客户端进行对应,如何实现一对多下面说)
/**
* 发送消息线程----------------------------------------------------------------------------
*/
class Send extends Thread{
private Socket socket;
private Scanner scanner;
public Send(Socket socket,Scanner scanner){
this.socket = socket;
this.scanner = scanner;
}
@Override
public void run() {
this.sendMsg();
}
/**
* 发送消息
*/
private void sendMsg(){
//创建向对方输出消息的流对象
try(PrintWriter pw = new PrintWriter(this.socket.getOutputStream())){
while(true){
String msg = scanner.nextLine();
pw.println(msg);
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 接收消息的线程---------------------------------------------------------------------------
*/
class Receive extends Thread{
private Socket socket;
public Receive(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.receiveMsg();
}
/**
* 用于接收对方消息的方法
*/
private void receiveMsg(){
//创建用于接收对方发送消息的流对象
try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
while(true){
String msg = br.readLine();
System.out.println("他说:"+msg);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
//主线程----------------------------------------------------------------------------------
public class GoodTCP {
public static void main(String[] args) {
Scanner scanner = null;
ServerSocket serverSocket = null;
Socket socket = null;
try{
scanner = new Scanner(System.in);
System.out.println("请输入:server,<port> 或者:<ip>,<port>");
String str = scanner.nextLine();
String[] arr = str.split(",");
if("server".equals(arr[0])){
//启动服务端
System.out.println("TCP Server Listen at "+arr[1]+" .....");
serverSocket = new ServerSocket(Integer.parseInt(arr[1]));
socket = serverSocket.accept();
System.out.println("连接成功!");
}else{
//启动客户端
socket = new Socket(arr[0],Integer.parseInt(arr[1]));
System.out.println("连接成功!");
}
//启动发送消息的线程
new Send(socket,scanner).start();
//启动接收消息的线程
new Receive(socket).start();
}catch(Exception e){
e.printStackTrace();
}finally{
if(serverSocket != null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
6 一对多通讯—设计
(1 之前在主线程当中去创建的ServerSocket的对象,然后通过ServerSocket.accept方法来负责监听端口等待客户端的链接,如果这个时候有客户端根据IP和端口来访问来链接这个Serverocket了,那么这个ServerSocket.accept方法会返回与这个客户端所对应的Socket对象
——>我们把这个Socket对象传递到了用于发送消息的线程,又传递到了用于接收消息的线程
——>然后主线程结束了,accept方法也不会再执行了——>再有客户端来连接这个服务端也无法连接了
——>这是不能实现一个服务端对应多个客户端的一个最根本的原因)
(2 只要保证这个server Socket的点accept这个方法,也就是说这一块的代码,让它一直在运行就可以了,只要它一直在运行,只要有客户端来链接,他就会返回与这个客户端对应的Socket对象——>想对多个客户端进行连接就要获得多个Socket对象)
(3 如果有多个客户端来链接服务器的话——>客户端和客户端之间是!!!相互隔离的!!!——>他们之间是完全不能做消息共享的——>比如京东服务器链接很多用户,每个人只能看自己的购物车)
(4 把Socket socket =serversocket.accept();放在死循环里面,监听到之后再监听)
7 一对多—应答型服务器
(允许多个客户端来连接它,其次是支持当客户端向它发送消息时——>它会将这个消息原封不动的返回给客户端)
(1 主线程——>创建ServerSocket对象,把Socket socket = serverSocket.accept();放在死循环里)
(2 应答型服务器只需要一个线程就可以——>构造方法传进来socket,重写run方法,将从客户端读取到的消息写回给客户端——>读和写——>要创建BufferedReader,InputStreamReader,PrintWriter对象)
/**
* 定义消息处理线程类-----------------------------------------------------------------------
*/
class Msg extends Thread{
private Socket socket;
public Msg(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.msg();
}
/**
* 将从客户端读取到的消息写回给客户端
*/
private void msg(){
try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
PrintWriter pw = new PrintWriter(this.socket.getOutputStream())){
while(true){
pw.println(br.readLine()+" [ok]");
pw.flush();
}
}catch(Exception e){
e.printStackTrace();
System.out.println(this.socket.getInetAddress()+" 断线了!");
}
}
}
//主线程----------------------------------------------------------------------------------
public class EchoServer {
public static void main(String[] args) {
try(ServerSocket serverSocket = new ServerSocket(8888)){
//等待多客户端连接
while(true){
Socket socket = serverSocket.accept();
new Msg(socket).start();
}
}catch(Exception e){
e.printStackTrace();
}
}
}
8 一对多—聊天型服务器
(聊天服务器——>一个客户端所发送的信息——>这些消息服务器要负责发送给所有的客户端,让其他的客户端也能看到你当前客户端所发送的内容)
(服务器线程设计——>理解为接收线程就是消息的生产者,发送线程就是消息的接收者,我们现在要把这个交给发送线程将数据发送给不同的客户端——>在生产者与消费者这个模式当中对于数据的处理时需要依赖于一个数据缓冲区)
(线程说明——>服务器启动以后,缓冲区里面还没有东西,所有用于向客户端发送消息的线程都处于一个等待的状态——>什么时候公共数据区有消息了再去发送消息——>公共数据区的数据来源于接收消息的线程——>当有某个客户端向服务端发送数据了,那么这个时候在服务端用于接收客户端消息的线程会拿到这个数据——>把数据放到公共数据区里——>对于公共数据区的访问而言,接收线程和发送线程肯定是一个线程同步的——>在访问公共数据区时肯定会让这个线程拥有相同的对象锁的——>然后当接收线程把消息放到公共数据区以后——>去唤醒同一把锁的所有的线程(发送消息的线程)notifyALL——>唤醒所以的发送消息线程——>这些线程去公共数据区当中拿到数据并且把数据发送给对应的客户端——>发送消息的线程又处于等待状态)
(1 主线程——>创建ServerSocket对象,把Socket socket = serverSocket.accept();放在死循环里——>定义公共数据区(字符串)static类型的(唯一性) )
(2 发送消息的线程类——>与服务端对应的socket对象,构造方法传递,重写run方法,创建PrintWriter对象,while(true)一直发送,——>解决线程冲突synchronized (“abc”)给字符串——>让发送消息的线程处于等待状态wait()方法——>之后如果被唤醒——>将公共数据区中的消息发送给客户端)
(3 接收客户端消息的线程类——>构造方法得到socket,重写run方法,创建用于接收对方发送消息的流对象BufferedReader 和InputStreamReader——>while(true)一直读取——> synchronized (“abc”)让他们拥有相同的锁——>把读取到的数据写入公共数据区——>唤醒发送消息的线程对象——>notifyAll())
服务器设计
-
服务器的连接设计
-
服务器的线程设计
/**
* 接收客户端消息的线程类--------------------------------------------------------------------
*/
class ChatReceive extends Thread{
private Socket socket;
public ChatReceive(Socket socket){
this.socket =socket;
}
@Override
public void run() {
this.receiveMsg();
}
/**
* 实现接收客户端发送的消息
*/
private void receiveMsg(){
try(BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
while(true){
String msg = br.readLine();
synchronized ("abc"){
//把读取到的数据写入公共数据区
ChatRoomServer.buf="["+this.socket.getInetAddress()+"] "+msg;
//唤醒发送消息的线程对象。
"abc".notifyAll();
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
/**
* 向客户端发送消息的线程类------------------------------------------------------------------
*/
class ChatSend extends Thread{
private Socket socket;
public ChatSend(Socket socket){
this.socket = socket;
}
@Override
public void run() {
this.sendMsg();
}
/**
* 将公共数据区的消息发送给客户端
*/
private void sendMsg(){
try(PrintWriter pw = new PrintWriter(this.socket.getOutputStream())){
while(true){
synchronized ("abc"){
//让发送消息的线程处于等待状态
"abc".wait();
//将公共数据区中的消息发送给客户端
pw.println(ChatRoomServer.buf);
pw.flush();
}
}
}catch(Exception e){
e.printStackTrace();
}
}
}
//主线程----------------------------------------------------------------------------------
public class ChatRoomServer {
//定义公共数据区
public static String buf;
public static void main(String[] args) {
System.out.println("Chat Server Version 1.0");
System.out.println("Listen at 8888.....");
try(ServerSocket serverSocket = new ServerSocket(8888)){
while(true){
Socket socket = serverSocket.accept();
System.out.println("连接到:"+socket.getInetAddress());
new ChatReceive(socket).start();
new ChatSend(socket).start();
}
}catch(Exception e){
e.printStackTrace();
}
}
}