基于TCP协议的网络编程

TCP/IP协议是一种可靠的网络协议,它的通信的两端各自建立一个Socket,从而在通信的两端之间形成网络虚拟链路。

Java使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信。

1、ServerSocket

java中能接收其他通信实体连接请求的类是ServerSocket,他会监听来自客户端Socket连接,如果没有连接,它将一直处于等待状态。

ServerSocket常用方法:

Socket accept():如果接收到客户端Socket的连接请求,返回一个与客户端Socket对应的Socket(每个TCP连接有两个Socket),否则一直处于等待状态,线程被阻塞。

ServerSocket构造器:

ServerSocket(int port):指定端口创建ServerSocket.

ServerSocket(int port ,int backlog):增加一个用来改变连接队列长度的参数backlog.

ServerSocket(int port ,int backlog ,InetAddress localAddr):机器有多IP时候,可以指定IP创建ServerSocket.

2、使用Socket进行通信

客户端可以使用Socket的构造器连接到指定服务器,Socket有两个构造器:

Socket(InetAddress/String remoteAddress,int port):创建连接到指定远程主机、远程端口的Socket,该构造器没有指定本地地址、本地端口,默认使用本机默认ip,默认使用系统动态分配的端口。

Socket(InetAddress/String remoteAddress,int port,InetAddress localAddr,int localPort):穿件Socket带有本地IP和port

package net;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket; public class SocketServer { public static void main(String[] args) throws IOException {
//定义服务端口号
int port = 7000;
//获取ServerSocket对象
ServerSocket serverSocket = new ServerSocket(port);
while(true) {
//监听来自客户端的连接
Socket socket =serverSocket.accept();
PrintStream pis = new PrintStream(socket.getOutputStream());
pis.print("Hi:来自服务端的问候");
pis.close();
socket.close();
} }
} package net;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
import java.net.UnknownHostException;
public class SocketClient { public static void main(String[] args) { //定义客户端端口
int port = 7000;
//获取Socket
Socket socket = null;
BufferedReader br = null;
try {
socket = new Socket("localhost",port);
//设置10s超时
socket.setSoTimeout(10000);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//获取数据
System.out.println(br.readLine());
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
} }
}

3、加入多线程

上面程序只是简单的实现了Server和Client的简单通信:服务器接收到客户端的连接后想客户端发送了一个字符串,客户端读取字符串后就退出了。

实际应用中客户端可能需要跟服务器保持长时间通信,在使用传统的BufferReader的readLine方法读取数据时,在该方法返回成功之前,线程被阻塞,考虑到此点,所以服务器端应该为每个Socket单独启动一个线程,负责与客户端通信。

下面程序实现了命令行聊天大厅功能(参考疯狂java讲义)

服务端:

package chatServer;

/**
* 协议字符
* @author rdb
*
*/
public interface ChatProtocol { //协议字符串长度
int PROTOCOL_LEN = 2;
//公发前后端字符
String MSG_ROUND = "☾△";
//用户前后字符
String USER_ROUND = "△▽";
//登录成功表示
String LOGIN_SUCCESS = "1";
//用户重复标识
String NAME_REP = "-1" ;
//私发用户标识
String PRIVATE_ROUND = "◆◆" ; String SPLIT_SIGN = "◆" ;
} package chatServer;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
*
* ChaMap:保存用户和对应的输出流,一一对应,不允许出现value重复
* @author rdb
* @param <K>
* @param <V>
*/
public class ChatMap<K,V> { //创建一个线程安全的HashMap
public Map<K,V> map = Collections.synchronizedMap(new HashMap<>()); //获取value的set集合
public synchronized Set<V> valueSet(){
Set<V> set = new HashSet<>();
map.forEach((key,value) -> set.add(value));
return set ;
} //实现put方法,不允许出现重复value
public synchronized V put(K key,V value) {
for(V v : valueSet()) {
if(v.equals(value) && v.hashCode() == value.hashCode()) {
throw new RuntimeException("ChatMap中不允许有重复value");
}
}
return map.put(key, value);
} //根据value查找key
public synchronized K getKeyByValue(V value) {
for(K k : map.keySet()) {
if(map.get(k) == value && map.get(k).equals(value)) {
return k;
}
}
return null;
} //根据value删除
public synchronized void removeByValue(V value){
for(K k : map.keySet()) {
if(map.get(k) == value) {
map.remove(k);
break;
}
}
}
} package chatServer;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ChatServer { private static final int SERVER_PORT = 30000 ;
//用户保存客户端用户和对应socket输出流
public static ChatMap<String, PrintStream> chatMap = new ChatMap<>(); public void init() {
try {
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
while(true) {
Socket socket = serverSocket.accept();
System.out.println("******");
//启动对应的线程处理对应的客户端
new Thread(new ServerThread(socket)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
} public static void main(String[] args) {
ChatServer chatServer = new ChatServer();
chatServer.init();
}
} package chatServer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;
public class ServerThread implements Runnable { private Socket socket = null;
private BufferedReader br = null;
private PrintStream ps = null; public ServerThread(Socket socket) {
this.socket = socket;
} @Override
public void run() {
try {
// 获取socket对应的输入流
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 获取socket对应的输出流
ps = new PrintStream(socket.getOutputStream());
// 获取客户端的消息
String line = null;
while ((line = br.readLine()) != null) {
// 判断信息类型
// 用户登录
if (line.startsWith(ChatProtocol.USER_ROUND) &&
line.endsWith(ChatProtocol.USER_ROUND)) {
String userName = getRealMsg(line);
// 如果包含该用户,返回重复登录
if (ChatServer.chatMap.map.containsKey(userName)) {
System.out.println("重复");
ps.println(ChatProtocol.NAME_REP);
} else {
System.out.println("成功");
ps.println(ChatProtocol.LOGIN_SUCCESS);
ChatServer.chatMap.put(userName, ps);
}
}
// 私聊
else if (line.startsWith(ChatProtocol.PRIVATE_ROUND) &&
line.endsWith(ChatProtocol.PRIVATE_ROUND)) {
String userAndMsg = getRealMsg(line);
// 私聊时 SPLIT_SIGN前是私聊对象,后面是私聊信息
String user = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[0];
String msg = userAndMsg.split(ChatProtocol.SPLIT_SIGN)[1];
// 向对应的客户端发送消息
ChatServer.chatMap.map.get(user).
println(ChatServer.chatMap.getKeyByValue(ps) + "悄悄的对你说:" + msg);
}
// 公聊
else {
String msg = getRealMsg(line);
// 向每个用户发送消息
ChatServer.chatMap.map.forEach((key, value) ->
value.println(ChatServer.chatMap.getKeyByValue(ps) + "说:" + msg));
}
}
}
// 捕获到异常表示对应的Socket的客户端出现问题,在map中移除该客户端信息
catch (IOException e) {
ChatServer.chatMap.removeByValue(ps);
System.out.println(ChatServer.chatMap.map.size());
// 关闭相关资源
try {
if (br != null) {
br.close();
}
if (ps != null) {
ps.close();
}
if (socket != null) {
socket.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
} private String getRealMsg(String msg) {
return msg.substring(ChatProtocol.PROTOCOL_LEN, msg.length() - ChatProtocol.PROTOCOL_LEN);
} }

客户端:

package charClient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket; import javax.swing.JOptionPane; /**
* 客户端
* @author rdb
*
*/
public class ChatClient { private static final int SERVER_PORT = 30000;
private static final String SERVER_IP = "127.0.0.1";
private Socket socket = null ;
private BufferedReader br = null;
private PrintStream ps = null ;
//键盘输入
private BufferedReader keyIn = null ; public void init() {
try {
socket = new Socket(SERVER_IP,SERVER_PORT);
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
ps = new PrintStream(socket.getOutputStream());
keyIn = new BufferedReader(new InputStreamReader(System.in)); String tip = "";
//要求输入用户名登录
while(true) {
String userName =JOptionPane.showInputDialog(tip + "输入用户名");
//将用户名发送到服务器
ps.println(ChatProtocol.USER_ROUND + userName + ChatProtocol.USER_ROUND);
//获取服务器返回的消息
String result = br.readLine();
if(result.equals(ChatProtocol.NAME_REP)) {
System.out.println("用户名重复,请重试");
continue;
}
if(result.equals(ChatProtocol.LOGIN_SUCCESS)) {
break ;
}
}
} catch (IOException e) {
System.out.println("网络异常,请重新登录");
closeRs();
System.exit(1);
}
//启动对应socket的读取服务器消息的线程
new Thread(new ClientThread(br)).start(); String line = null;
try {
while((line = keyIn.readLine()) != null) {
//如果发送信息中有:号,且以//开头,则认为是发送私聊信息://张三:你最近怎么样
if(line.indexOf(":") >0 && line.startsWith("||")) {
line = line.substring(2);
System.out.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0]
+ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND);
ps.println(ChatProtocol.PRIVATE_ROUND+line.split(":")[0]
+ChatProtocol.SPLIT_SIGN+line.split(":")[1]+ChatProtocol.PRIVATE_ROUND);
}else {
ps.println(ChatProtocol.MSG_ROUND + line + ChatProtocol.MSG_ROUND);
} }
} catch (IOException e) {
System.out.println("网络异常,请重新登录");
closeRs();
System.exit(1);
} } private void closeRs() {
try {
if(br != null) {
br.close();
}
if(ps != null) {
ps.close();
}
if(socket != null) {
socket.close();
}
if(keyIn != null) {
keyIn.close();
}
} catch (IOException e) {
e.printStackTrace();
}
} public static void main(String[] args) {
ChatClient chatClient = new ChatClient();
chatClient.init();
}
} package charClient; import java.io.BufferedReader;
import java.io.IOException; public class ClientThread implements Runnable{
private BufferedReader br = null;
public ClientThread(BufferedReader br) {
this.br = br ;
}
@Override
public void run() {
String line = null;
try {
while((line = br.readLine()) != null){
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(br != null) {
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} package charClient; /**
* 协议字符
* @author rdb
*
*/
public interface ChatProtocol { //协议字符串长度
int PROTOCOL_LEN = 2;
//公发前后端字符
String MSG_ROUND = "☾△";
//用户前后字符
String USER_ROUND = "△▽";
//登录成功表示
String LOGIN_SUCCESS = "1";
//用户重复标识
String NAME_REP = "-1" ;
//私发用户标识
String PRIVATE_ROUND = "◆◆" ; String SPLIT_SIGN = "◆" ;
}

java网络编程基础——TCP网络编程一的更多相关文章

  1. java网络编程基础——TCP网络编程二

    1.半关闭的Socket 前面的服务器和客户端通信时总是以行为最小数据单位,但是在某些协议里,通信的数据单位可能是多行的,当出现多行数据时就 出现一个问题:Socket输出流如何表示输出数据已经结束. ...

  2. java网络编程基础——TCP网络编程三

    AIO实现非阻塞通信 java7 NIO2 提供了异步Channel支持,这种异步Channel可以提供更高效的IO,这种基于异步Channel的IO被称为异步IO(Asynchronous IO) ...

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

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

  4. 【Linux网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系

    [Linux网络编程]TCP网络编程中connect().listen()和accept()三者之间的关系 基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: conn ...

  5. java网络编程基础——基本网络支持

    基本网络支持 java.net包主要为网络编程提供支持. 1.InetAddress InetAddress类代表IP地址,还有两个子类:Inet4Address.Inet6Address. pack ...

  6. 网络编程基础:网络基础之网络协议、socket模块

    操作系统(简称OS)基础: 应用软件不能直接操作硬件,能直接操作硬件的只有操作系统:所以,应用软件可以通过操作系统来间接操作硬件 网络基础之网络协议: 网络通讯原理: 连接两台计算机之间的Intern ...

  7. UNIX网络编程——解决TCP网络传输“粘包”问题

    当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...

  8. 【Linux 网络编程】TCP网络编程中connect()、listen()和accept()三者之间的关系

    基于 TCP 的网络编程开发分为服务器端和客户端两部分,常见的核心步骤和流程如下: connect()函数:对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三 ...

  9. 【网络编程】TCP网络编程

    TCP网络程序设计 一.模型函数化 图片:TCP编程模型 1.1 创建套接字Socket #include <sys/types.h> /* See NOTES */ #include & ...

随机推荐

  1. AIoT 2020 年分析

    AIoT 2020 年分析 2020年,从智能手机到智能手表,从智能摄像头到智能汽车,随着AI.芯片.云计算.通信等基础技术的逐渐成熟,又一个行业来到了历史性的时刻--AIoT. 从"万物互 ...

  2. 对端边缘云网络计算模式:透明计算、移动边缘计算、雾计算和Cloudlet

    对端边缘云网络计算模式:透明计算.移动边缘计算.雾计算和Cloudlet 概要 将数据发送到云端进行分析是过去几十年的一个突出趋势,推动了云计算成为主流计算范式.然而,物联网时代设备数量和数据流量的急 ...

  3. 用NVIDIA A100 GPUs提高计算机视觉

    用NVIDIA A100 GPUs提高计算机视觉 Improving Computer Vision with NVIDIA A100 GPUs 在2020年英伟达GPU技术会议的主题演讲中,英伟达创 ...

  4. TensorRT 7.2.1 开发概要(下)

    TensorRT 7.2.1 开发概要(下) 1.2. Where Does TensorRT Fit? 一般来说,开发和部署深度学习模型的工作流要经过三个阶段. Phase 1 is trainin ...

  5. springboot注解之@ConditionalOnProperty

    最近在研究springboot的源码,看到很多@ConditionalOnXxx的注解,大概明白此注解的意思,就是判断条件,这个条件就是Xxx,例如ConditionalOnProperty就是判断配 ...

  6. 使用ElementTree解析,操作xml

    一.最近在实际工作中需要对一部分接口进行测试,接口的入参与出参都是xml格式的数据,所以用到了python内部模块ElementTree对xml进行解析,并根据实际需求操作xml数据 二.代码示例 # ...

  7. WordPress安全篇(1):WordPress网站启用HTTPS详细教程

    以前我们浏览网页使用的都是HTTP协议,HTTP使用明文传输,所以传输过程中很容易遭受黑客窃取.篡改数据,很不安全.在WordPress网站上启用HTTPS协议访问后,能大大提升站点的安全性,启用HT ...

  8. SpringCloud Alibaba实战(8:使用OpenFeign服务调用)

    源码地址:https://gitee.com/fighter3/eshop-project.git 持续更新中-- 在上一个章节,我们已经成功地将服务注册到了Nacos注册中心,实现了服务注册和服务发 ...

  9. 安卓开发(3)—1— Activity

    安卓开发(3)-1- Activity 3.1 Activity是什么: 在前面安卓概述中有提到,Activity是Android开发中的四大组件,所有在app里可以看到的东西都是Activity里面 ...

  10. 把新建的vue项目上传到码云

    1:在码云上建一个仓库(使用Readme文件初始化这个项目的勾取消掉) 2:在项目文件中打开git命令窗口(如下图),命令git init 初始化git仓库 运行之后有一个.git文件夹 现在用vsc ...