1、Java中客户端和服务器端通信的简单实例

Java中能接收其他通信实体连接请求的类是ServerSocket,ServerSocket对象用于监听来自客户端的Socket连接,如果没有连接,它将一直处于等待状态 ServerSocket包含一个监听来自客户端连接请求的方法。
ServerSocket accept():  接收到一个客户端Socket的连接请求,该方法将返回一个与客户端Socket对应的Socket。如果没有客户端连接上来,该方法将一直处于等待状态,线程也被阻
塞。
客户端:Socket(String host, int port) 创建流套接字并将其连接到指定主机上的指定端口号。  
服务端:ServerSocket(int port, int backlog) 创建服务器套接字并使用指定的待办事项将其绑定到指定的本地端口号。 
 
 服务器端ServerSocket代码:
public class ServerTest {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(12345);
/**
* 死循环接收客户端请求,accept会阻塞
*/
while (true){
//阻塞等待客户端连接
Socket clientSocket = serverSocket.accept();
try(PrintStream outputStream = new PrintStream(clientSocket.getOutputStream())) {
outputStream.println("Hello,请求已收到。");
}finally {
clientSocket.close();
}
}
}
}

客户端Socket:

public class ClientTest {
public static void main(String[] args) throws IOException {
try (Socket clientSocket = new Socket("127.0.0.1",12345);
BufferedReader clientIn = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));){
String line = clientIn.readLine();
System.out.println("收到服务端返回:" + line);
} }
}

2、采用多线程方式处理网络请求

前面的Server,Client程序只是进行了简单的通信操作,服务器端接收到客户端连接之后,服务器端向客户端输出了一个字符串,而客户端也只是读取服务器端的字符串后就退出了。实际应用中的客户端则可能需要和服务器端保持长时间通信,即服务器端需要不断地读取客户端数据,并向客户端写入数据,户端也需要不断地读取服务器端数据,并向服务器端写入数据。
比如我们的QQ群发消息,比如群里面有100个人上线,就有100个客户端连上了服务器,我们读取到用户发上来的消息之后,我们还要同步给剩余的99个人。这个时候如果我们使用同步的方式处理,会造成在处理我们具体的业务逻辑的时候(如果耗时比较长),我们无法获取到其他用户连接上来的Socket。
群发消息服务器端:
package tcpandudp.threadssockettest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set; /**
* @ClassName Server
* @projectName: object1
* @author: Zhangmingda
* @description: 群发消息
* date: 2021/5/11.
*/
public class Server {
private static Set<Socket> sockets = Collections.synchronizedSet(new HashSet<>());
private static class MyRunnable implements Runnable {
/**
* 当前线程要处理的客户端Socket
*/
private Socket socket; /**
* @param socket 构造客户端socket
*/
public MyRunnable(Socket socket) {
this.socket = socket;
} /**
* 具体处理每个客户端连接请求的逻辑,封装到线程run方法中
*/
@Override
public void run() {
//获取客户端发送来的数据
String content = null;
try (BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
/**
* 每次读取一行,读取一行就广播给所有客户端,直到读尽,阻塞住,
*/
while ((content = br.readLine()) != null){
//广播给所有客端 通过socket.getOutputStream()获取输出流OutputStream
for (Socket s : sockets){
PrintStream printStream = new PrintStream(s.getOutputStream());
printStream.println(content);
}
}
}catch (SocketException e){
System.out.println(socket.getInetAddress() + "已断开");;
}
catch (IOException e) {
e.printStackTrace();
}finally {
/**
* 当客户端断开连接while循环跳出,关闭客户端连接,从客户端Set集合中移除客户端Socket
*/
sockets.remove(this.socket);
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
this.socket = null;
}
}
}
}
public static void main(String[] args) throws IOException {
/**
* 服务端监听端口
*/
ServerSocket serverSocket = new ServerSocket(8888);
/**
* 启动后一直等待客户端连接,获取到一个客户端就保存到客户端集合中,
* 然后启动一个新的线程处理对应客户端请求,
* 主线程再次等待新的客户端建联
*/
while (true){
Socket client = serverSocket.accept();
sockets.add(client);
new Thread(new MyRunnable(client)).start();
}
}
}
客户端:
package tcpandudp.threadssockettest;

import java.io.*;
import java.net.Socket; /**
* @ClassName ClientTest
* @projectName: object1
* @author: Zhangmingda
* @description: XXX
* date: 2021/5/11.
*/
public class ClientTest {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",8888);
/**
* 启动一个线程持续读取服务端返回的数据
*/
new Thread(){
@Override
public void run() {
String content = null;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()))){
//阻塞式等待服务端回复的数据
while ((content = bufferedReader.readLine()) != null){
System.out.println("收到服务端返回数据:" + content);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
/**
* 主线程发送数据
*/
try (PrintStream printStream = new PrintStream(socket.getOutputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in))
){
String line = null;
/**
* 如果输入的不是exit字符串,就发送输入的数据,然后一直死循环阻塞等待再次输入数据
* 获取到了字符,内容是exit while循环false 退出
*/
while ((line = bufferedReader.readLine()) != null && !line.equals("exit")){
printStream.println(line);
}
}
}
}

使用线程安全的ConcurrentHashMap记录客户端Socket信息(要求客户端连接后提供用户名,否则不加入群聊):

package tcpandudp.threadssockettest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; /**
* @ClassName Server
* @projectName: object1
* @author: Zhangmingda
* @description: 群发消息
* date: 2021/5/11.
*/
public class ServerRemberUsername {
/**
* 记录客户端信息的线程安全Map
*/
private static ConcurrentHashMap<Socket,String> sockets = new ConcurrentHashMap<>(); /**
* 单线程处理逻辑
*/
private static class MyRunnable implements Runnable {
/**
* 当前线程要处理的客户端Socket
*/
private Socket socket; /**
* @param socket 构造客户端socket
*/
public MyRunnable(Socket socket) {
this.socket = socket;
} /**
* 具体处理每个客户端连接请求的逻辑,封装到线程run方法中
*/
@Override
public void run() {
//获取客户端发送来的数据
String content = null;
try (BufferedReader br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()))){
/**
* 每次读取一行,读取一行就广播给所有客户端,直到读尽,阻塞住,
*/
while ((content = br.readLine()) != null){
/**
* 判断是否要设置用户名
*/
if (content.startsWith("username:")){
PrintStream printStream = new PrintStream(socket.getOutputStream());
if (sockets.keySet().contains(socket)){
printStream.println("您已设置用户名:" +sockets.get(socket) + "无需重新设置");
}else {
String username = "";
//格式正确
if (content.split(":").length > 1){
//获取用户名并去除前后空格
username = content.split(":")[1].trim();
System.out.println("username:"+username);
//用户名不为空设置用户名
if (! "".equals(username)){
sockets.put(socket,username);
printStream.println("成功设置用户名:"+username);
}else {
printStream.println("用户名不允许为空");
}
}
else {
printStream.println("用户名设置有误,格式为username:XXXXX");
}
}
}else {
/**
* 普通消息判断是否已登录,登录后发消息否则提示先登录
*/
if (sockets.keySet().contains(socket)){
//广播给所有客端 通过socket.getOutputStream()获取输出流OutputStream
for (Socket s : sockets.keySet()){
PrintStream printStream = new PrintStream(s.getOutputStream());
printStream.println(sockets.get(socket) + ":" + content);
}
}else {
//未登录提示先登录
PrintStream printStream = new PrintStream(socket.getOutputStream());
printStream.println("请先登录");
}
}
}
}catch (SocketException e){
System.out.println(socket.getInetAddress() + "已断开");;
}
catch (IOException e) {
e.printStackTrace();
}finally {
/**
* 当客户端断开连接while循环跳出,关闭客户端连接,从客户端Set集合中移除客户端Socket
*/
sockets.remove(this.socket);
try {
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
this.socket = null;
}
}
}
}
public static void main(String[] args) throws IOException {
/**
* 服务端监听端口
*/
ServerSocket serverSocket = new ServerSocket(8888);
/**
* 启动后一直等待客户端连接,获取到一个客户端就保存到客户端集合中,
* 然后启动一个新的线程处理对应客户端请求,
* 主线程再次等待新的客户端建联
*/
while (true){
Socket client = serverSocket.accept();
new Thread(new MyRunnable(client)).start();
}
}
}
 

java 网络编程基础 TCP/IP协议:服务端ServerSocket;客户端Socket; 采用多线程方式处理网络请求的更多相关文章

  1. 嵌入式linux的网络编程(1)--TCP/IP协议概述

    嵌入式linux的网络编程(1)--TCP/IP协议概述 1.OSI参考模型及TCP/IP参考模型 通信协议用于协调不同网络设备之间的信息交换,它们建立了设备之间互相识别的信息机制.大家一定都听说过著 ...

  2. 【转载】[基础知识]【网络编程】TCP/IP

    转自http://mc.dfrobot.com.cn/forum.php?mod=viewthread&tid=27043 [基础知识][网络编程]TCP/IP iooops  胖友们楼主我又 ...

  3. 【网络编程1】网络编程基础-TCP、UDP编程

    网络基础知识 网络模型知识 OSI七层模型:(Open Systems Interconnection Reference Model)开放式通信系统互联参考模型,是国际标准化组织(ISO)提出的一个 ...

  4. DSAPI多功能组件编程应用-HTTP监听服务端与客户端_指令版

    前面介绍了DSAPI多功能组件编程应用-HTTP监听服务端与客户端的内容,这里介绍一个适用于更高效更快速的基于HTTP监听的服务端.客户端. 在本篇,你将见到前所未有的超简化超傻瓜式的HTTP监听服务 ...

  5. QTcpSocket-Qt使用Tcp通讯实现服务端和客户端

    版权声明:若无来源注明,Techie亮博客文章均为原创. 转载请以链接形式标明本文标题和地址: 本文标题:QTcpSocket-Qt使用Tcp通讯实现服务端和客户端     本文地址:https:// ...

  6. 常量,字段,构造方法 调试 ms 源代码 一个C#二维码图片识别的Demo 近期ASP.NET问题汇总及对应的解决办法 c# chart控件柱状图,改变柱子宽度 使用C#创建Windows服务 C#服务端判断客户端socket是否已断开的方法 线程 线程池 Task .NET 单元测试的利剑——模拟框架Moq

    常量,字段,构造方法   常量 1.什么是常量 ​ 常量是值从不变化的符号,在编译之前值就必须确定.编译后,常量值会保存到程序集元数据中.所以,常量必须是编译器识别的基元类型的常量,如:Boolean ...

  7. TCP/IP网络编程之基于UDP的服务端/客户端

    理解UDP 在之前学习TCP的过程中,我们还了解了TCP/IP协议栈.在四层TCP/IP模型中,传输层分为TCP和UDP这两种.数据交换过程可以分为通过TCP套接字完成的TCP方式和通过UDP套接字完 ...

  8. java网络编程基础——TCP网络编程一

    基于TCP协议的网络编程 TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路. Java使用Socket对象来代表两端的通信端口,并通过 ...

  9. 网络基础tcp/ip协议一

    计算机网络: 硬件方面:通过线缆将网络设备和计算机连接起来 软件方面:操作系统,应用软件,应用程序通过通信线路互连 实现资源共享,信息传递 计算机网络的功能: 数据通信 资源共享 增加可靠性 提高系统 ...

随机推荐

  1. 洛谷 P6189 - [NOI Online #1 入门组]跑步(根号分治+背包)

    题面传送门 题意: 求有多少个数列 \(x\) 满足: \(\sum x_i=n\) \(x_i\geq x_{i+1}\) 答案对 \(p\) 取模. ...你确定这叫"入门"组 ...

  2. 洛谷 P5331 - [SNOI2019]通信(CDQ 分治优化建图+费用流)

    题面传送门 首先熟悉网络流的同学应该能一眼看出此题的建模方法: 将每个点拆成两个点 \(in_i,out_i\),连一条 \(S\to in_i\),容量为 \(1\) 费用为 \(0\) 的边 连一 ...

  3. 关闭 IDEA 自动更新

    关闭 IDEA 的自动检查更新(截图idea 2020 2.x) idea 右下角会有这样的更新提示 2. 关闭 idea 自动检查更新 取消勾选 Automatically check update ...

  4. Matlab 代码注释

    Matlab 代码注释 一直在找类似doxygen一样将程序注释发表成手册的方法,现在发现,Matlab的publish功能自己就能做到. Publish 简介 并非所有注释都能作为文本进行输出,MA ...

  5. C语言中的字符和整数之间的转换

    首先对照ascal表,查找字符和整数之间的规律: ascall 控制字符  48  0  49  1  50  2  51  3  52  4  53  5  54  6  55  7  56  8 ...

  6. C/C++ Qt 数据库与TableView多组件联动

    Qt 数据库组件与TableView组件实现联动,以下案例中实现了,当用户点击并选中TableView组件内的某一行时,我们通过该行中的name字段查询并将查询结果关联到ListView组件内,同时将 ...

  7. CPU如何同时运行多个进程?

    1 # -*- coding: utf-8 -*- 2 import re 3 mem = [x for x in re.split('[\r|\n]', ''' 4 store a 1 5 add ...

  8. Spark基础:(二)Spark RDD编程

    1.RDD基础 Spark中的RDD就是一个不可变的分布式对象集合.每个RDD都被分为多个分区,这些分区运行在分区的不同节点上. 用户可以通过两种方式创建RDD: (1)读取外部数据集====> ...

  9. Oracle参数文件—pfile与spfile

    oracle的参数文件:pfile和spfile 1.pfile和spfile       Oracle中的参数文件是一个包含一系列参数以及参数对应值的操作系统文件.它们是在数据库实例启动时候加载的, ...

  10. Advanced C++ | Virtual Copy Constructor

    这个不懂,等看会了再写...