基于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. python应用_读取Excel数据【二】_二次封装之函数式封装

    目的:想要把对Excel文件读取做成一个通用的函数式封装,便于后续简单调用,隔离复杂性. 未二次封装前原代码: #coding=gbkimport osimport xlrdcurrent_path= ...

  2. ES6函数参数

    之前在看Vue2.0的时候,就被很多的箭头函数困扰,一直不知道是怎么回事,虽然箭头函数四个字在我耳边一直转圈,今天小编整理整理箭头函数里面的常见用法和表现形式,在这个Vue3.0已经到来的一段时间,希 ...

  3. 单点突破:MySQL之基础

    前言 开发环境:MySQL5.7.31 本文并不是mysql语法语句的教程或者笔记,如果初学MySQL,想要看sql的教程或者学习各种语法语句规范,可以看看一千行MySQL学习笔记或者MySQL教程| ...

  4. git介绍及使用

    一.架构 版本库(仓库):工作区中有一个隐藏目录.git,这个目录不属于工作区,而是git的版本库,是git管理的所有内容. 暂存区:版本库中包含一个临时区域,保存下一步要提交的文件. 分支:版本库中 ...

  5. springMVC异常处理(自定义异常)HandlerExceptionResolver

    注:本篇的异常主要指的是controller.service和dao层中执行方法抛出的异常. 一.为什么要处理异常? 因为如果我们不处理异常,异常信息就会直接抛出给浏览器,于是浏览器页面就直接显示异常 ...

  6. 类编程的WAF(上)

    一.复杂的需求 WAF (WEB 应用防火墙) 用来保护 WEB 应用免受来自应用层的攻击.作为防护对象的 WEB 应用,其功能和运行环境往往是复杂且千差万别的,这导致即便防御某个特定的攻击方式时,用 ...

  7. 【SQLite】SQLite文件突然变大怎么办?瘦身办法

    使用VACUUM命令即可: VACUUM 命令通过复制主数据库中的内容到一个临时数据库文件,然后清空主数据库,并从副本中重新载入原始的数据库文件.这消除了空闲页,把表中的数据排列为连续的,另外会清理数 ...

  8. Java on Visual Studio Code的更新 – 2021年5月

    杨尧今 from Microsoft VS Code Java Team 欢迎来到 5月的 VS Code for Java 更新.这次,我们将与您分享Java格式化设置编辑器和其他很酷的功能.开始吧 ...

  9. css--常见左右盒子高度自适应布局

    前言 前端开发工程师最基础的技能要求是根据 ui 设计稿还原网页,这就缺少不了必要的网页布局,首先看下最近小伙伴问我的一个问题,他说一个网页有左右两个部分,左右两个部分的高度都不固定,要使得右部分的宽 ...

  10. WEB安全新玩法 [2] 防范前端验证绕过

    用户登录,几乎是所有 Web 应用所必须的环节.Web 应用通常会加入一些验证手段,以防止攻击者使用机器人自动登录,如要求用户输入图形验证码.拖动滑动条等.但是,如果验证的逻辑仅仅在前端执行,是很容易 ...