基于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. ISP算法高水平分析(下)

    ISP算法高水平分析(下)  十.LSC(Lens Shade Correction)------镜头阴影矫正 Lens Shading指画面四角由于入射光线不足形成的暗角,同时,由于不同频率的光折射 ...

  2. GPU虚拟化技术详解

    GPU虚拟化技术详解 GPU英文名称为Graphic Processing Unit,GPU中文全称为计算机图形处理器,1999年由NVIDIA公司提出. 一.GPU概述 GPU这一概念也是相对于计算 ...

  3. 5G和AI机器人平台

    5G和AI机器人平台 Qualcomm Launches 5G and AI Robotics Platform 高通技术公司(Qualcomm Technologies)周三推出了一款高级5G和人工 ...

  4. 使用IDEA创建Maven项目

    一.创建一个普通的Maven项目 1.启动IDEA 2.创建一个Maven项目 3.Maven的目录结构 二.使用模板创建一个MavenWeb项目 1.启动IDEA 2.创建一个MavenWeb项目 ...

  5. 【逆向&编程实战】Metasploit安卓载荷运行流程分析_复现meterpreter模块接管shell

    /QQ:3496925334 作者:MG193.7 CNBLOG博客号:ALDYS4 未经许可,禁止转载/ 关于metasploit的安卓模块,前几次的博客我已经写了相应的分析和工具 [Android ...

  6. 【NX二次开发】开发好几年,还只会用ufusr?其他用户出口函数介绍

    用户出口(User Exit)是NX Open 中的一个重要概念.NX在运行过程中某些特定的位置存在规定的出口,当进程执行到这些出口时,NX会自动检查用户是否在此处已定义了指向内部程序位置的环境变量: ...

  7. 大白话带你认识JVM

    如果在文中用词或者理解方面出现问题,欢迎指出.此文旨在提及和而不深究,但会尽量效率地把知识点都抛出来 一.JVM的基本介绍 JVM 是 Java Virtual Machine 的缩写,它是一个虚构出 ...

  8. Mysql优化(出自官方文档) - 第一篇(SQL优化系列)

    Mysql优化(出自官方文档) - 第一篇 目录 Mysql优化(出自官方文档) - 第一篇 1 WHERE Clause Optimization 2 Range Optimization Skip ...

  9. MySQL密码复杂度策略

    前言 MySQL5.6.6版本之后增加了密码强度验证插件validate_password,相关参数设置的较为严格.使用了该插件会检查设置的密码是否符合当前设置的强度规则,若不满足则拒绝设置. 本文采 ...

  10. 用Java语言编写的迷宫小游戏软件

    可查看本项目的github 源码链接,撒娇打滚求 star 哦~~ღ( ´・ᴗ・ ` )比心 本仓库代码是经过 eclipse 编译运行过的,一般情况下将本仓库代码下载下来之后,使用 eclipse ...