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. nginx安装与配置1-nginx安装

    反向代理: 客户端不需要配置就可以访问,将请求发送到反向代理服务器, 由反向代理服务器选择目标服务器获取数据,再返回客户端,对外暴露代理服务器地址,隐藏真实ip 负载均衡: 客户端请求nginx等服务 ...

  2. 『与善仁』Appium基础 — 12、Appium的安装详解

    目录 (一)Appium server安装 方式一:(桌面方式:推荐) 1.Appium Desktop下载 2.Appium Desktop安装 3.Appium Desktop使用 方式二:(No ...

  3. Atcoder Grand Contest 023 E - Inversions(线段树+扫描线)

    洛谷题面传送门 & Atcoder 题面传送门 毒瘤 jxd 作业-- 首先我们不能直接对所有排列计算贡献对吧,这样复杂度肯定吃不消,因此我们考虑对每两个位置 \(x,y(x<y)\), ...

  4. 洛谷 P7520 - [省选联考 2021 A 卷] 支配(支配树)

    洛谷题面传送门 真·支配树不 sb 的题. 首先题面已经疯狂暗示咱们建出支配树对吧,那咱就老老实实建呗.由于这题数据范围允许 \(n^2\)​ 算法通过,因此可以考虑 \(\mathcal O(n^2 ...

  5. Parallel NetCDF 简介

    Parallel NetCDF API 所有C接口前加ncmpi前缀,Fortran接口前加nfmpi前缀 函数返回整数 NetCDF 状态变量 1. Variable and Parameter T ...

  6. time 查看命令执行时间

    在命令执行完成之后就会打印出CPU的使用情况: real    0m5.064s      <== 实际使用时间(real time) user    0m0.020s     <== 用 ...

  7. 宏GENERATED_BODY做了什么?

    Version:4.26.2 UE4 C++工程名:MyProject \ 一般语境下,我们说c++源码的编译大体分为:预处理.编译.链接; cppreference-translation_phas ...

  8. 使用clion阅读eos源码

    配置mingw 安装clion 从github克隆源码 使用clion open打开 在cmake上使用boost: sudo apt-get install libboost-all-dev

  9. 巩固javaweb的第二十二天

    巩固内容: 使用表单数据 : 要对用户输入的信息进行验证,需要先获取输入信息.每个表单元素都属于一个 form 表单,要获取信息,需要先获取 form,然后访问表单元素的值. 有两种方式可以获取 fo ...

  10. 巩固javaweb第十五天

    巩固内容: 单选按钮: 在注册功能中,用户选择学历使用的是单选按钮,并且是多个单选按钮,每个选项对 应一个单选按钮,用户只能选择其中一个,这多个单选按钮的格式相同.如果用户要输入 的信息只有少数几种可 ...