文章目录
前言
网络编程的目的是为了跨主机通信,socket==》操作系统提供的网络编程的API就称为"socket"API
1.网络编程套接字
TCP和UDP都是传输层协议,都是给应用层提供服务的,由于这两个协议特点差异非常打,因此我们就需要两套API来分别表示。
1.1流式套接字(TCP)
1.1.1特点
- 有连接:在数据传输开始前,需要通信双方建立一个专用的通道,数据传输完成后再关闭连接,好比打电话,需要双方都接通才可以对话
- 可靠传输:在传输数据的时候,尽可能的将数据传输到达对方,并不是100%
- 面向字节流:面向对象是字节,在读写的操作会非常灵活。
- 全双工:一条链路中,能够进行双向通信。
1.1.2编码
TCP中的socket API重点是两个类:ServerSocket和Socket。
1.1.2.1ServerSocket
ServerSocket这个类主要是用于TCP的服务器的,里面有一个非常重要的方法accept,这个方法主要是让服务器与客户端建立连接的方法,没有建立连接是无法让服务器与客户端双方互相通信。
1.1.2.2Socket
Socket这个类是既可以用于服务器中也可以用于客户端中,这个类主要是以字节的形式将数据存储和传输的,所以这个类中提供的两种方法可以类比文件操作中的方法,这个类提供getInputStream()和getOutputStream()
前者是相当于文件操作中的读操作,后者类比于写操作,只是这里的操作对象换成了服务器与客户端了。
1.1.2.3实现一个TCP(7*24)回显服务器
在这里由于getInputStream()数据都是以字节的形式存在和传输的,但在处理响应的时候会转化为字符串的形式,为了不这么麻烦的去转化,我们就用Scanner来作为读操作,将getInputStream实现的对象放入Scanner构造方法中,让Scanner内部将字节数据转化为字符串的形式,这样就避免了之后程序中许多转化。
java
public class tcpEchoServer {
private ServerSocket serverSocket = null;
public tcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!!!");
//将服务器与客户端进行连接,连接成功返回一个Socket对象,后续服务器对客户端的操作都基于这个对象
while(true) {
Socket clientSocket = serverSocket.accept();
//创建一个方法来操作每一个客户端与服务器的联系
processConnection(clientSocket);
}
}
public void processConnection(Socket clienSocket) throws IOException {
//打印一个客户端上线的日志
System.out.printf("[%s:%d]客户端上线!\n",clienSocket.getInetAddress(),clienSocket.getPort());
//1.接收客户端发送过来的信息和并解析
try(InputStream inputStream = clienSocket.getInputStream();
OutputStream outputStream = clienSocket.getOutputStream()) {
//接收客户端发送过来的请求并解析(相当于从客户端读到服务器),用到Scanner的原因是由于InputStream读取的数据是字节的形式,
//后续处理数据的时候还是要将数据从字节形式转化为字符串,而Scanner内部可以帮我们直接转化。
Scanner scanner = new Scanner(inputStream);
//由于需要处理客户端不断发送过来的请求,我们就需要一个while循环
while(true) {
//处理客户端那边不发送请求的情况
if(!scanner.hasNext()) {
System.out.printf("[%s:%d]客户端下线!\n",clienSocket.getInetAddress(),clienSocket.getPort());
break;
}
//将从客户端接收过来的数据赋值到request中,为之后处理数据做铺垫
//这里的scanner.next()需要读到空白符才会停止,所以为了区分每一份的应用层的数据报,所以我们可以在客户端手动添加空白符,
//来控制每一份的应用层数据报
String request = scanner.next();
//2.计算响应,由于这是一个回显服务器,所以我们呢不需要对响应做处理,请求什么就返回什么响应
String response = process(request);
//3.将计算好的响应传回客户端(相当与将数据写回客户端)
outputStream.write(response.getBytes(),0,response.getBytes().length);
//4.打印服务器日志
System.out.printf("[%s:%d]res = %s,resp = %s\n",clienSocket.getInetAddress(),clienSocket.getPort(),
request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
clienSocket.close();
}
}
private String process(String request) {
return request + "\n";
}
public static void main(String[] args) throws IOException {
tcpEchoServer server = new tcpEchoServer(9999);
server.start();
}
}
程序走到这里有两种情况:
- 服务器与客户端双方之间的连接还在,但是客户端并没有发送请求,则会在此处进行阻塞,直到客户端发送请求过来,才会解除阻塞
- 服务器与客户端双方之间的连接已经断开了,那么直接就执行了if中的语句
关于if中的scanner怎么判断连接存在还是不存在的情况,scanner是无法判断的,scnner只有阻塞作用,判断连接是否存在是scoket来判断的。
1.1.2.4实现一个TCP的客户端
java
public class tcpEchoClient {
public Socket socket = null;
public tcpEchoClient(String serverIp,int serverProt) throws IOException {
socket = new Socket(serverIp,serverProt);
}
public void start() throws IOException {
//打印日志
System.out.println("客户端启动!");
//1.从控制台读取
Scanner scanner = new Scanner(System.in);
try (InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream()){
Scanner scannerNetwork = new Scanner(inputStream);
while(true) {
System.out.println("请输入你的请求:");
String request = scanner.next();
//与服务器规定的规则相对应
request += "\n";
//2.发送请求到服务器(写入服务器中)
outputStream.write(request.getBytes());
//3.接收从服务器发送过来的响应(从服务器将响应读入客户端)
if(!scannerNetwork.hasNext()) {
break;
}
String response = scannerNetwork.next();
//4.将回应打印到控制台
System.out.println(response);
}
}
}
public static void main(String[] args) throws IOException {
tcpEchoClient client = new tcpEchoClient("127.0.0.1",9999);
client.start();
}
}
上述的服务器还是存在一些问题的,比如:无法处理多个客户端同时来访问服务器,解决这个问题可以使用多线程让一个主线程来创建多个服务器与客户端连接的对象,然后再用其他线程来给客户端提供服务。
java
public class tcpEchoServer {
private ServerSocket serverSocket = null;
public tcpEchoServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
}
public void start() throws IOException {
System.out.println("服务器启动!!!");
ExecutorService pool = Executors.newCachedThreadPool();
//将服务器与客户端进行连接,连接成功返回一个Socket对象,后续服务器对客户端的操作都基于这个对象
while(true) {
Socket clientSocket = serverSocket.accept();
pool.submit(new Runnable() {
@Override
public void run() {
try {
processConnection(clientSocket);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
});
}
}
public void processConnection(Socket clienSocket) throws IOException {
//打印一个客户端上线的日志
System.out.printf("[%s:%d]客户端上线!\n",clienSocket.getInetAddress(),clienSocket.getPort());
//1.接收客户端发送过来的信息和并解析
try(InputStream inputStream = clienSocket.getInputStream();
OutputStream outputStream = clienSocket.getOutputStream()) {
//接收客户端发送过来的请求并解析(相当于从客户端读到服务器),用到Scanner的原因是由于InputStream读取的数据是字节的形式,
//后续处理数据的时候还是要将数据从字节形式转化为字符串,而Scanner内部可以帮我们直接转化。
Scanner scanner = new Scanner(inputStream);
//由于需要处理客户端不断发送过来的请求,我们就需要一个while循环
while(true) {
//处理客户端那边不发送请求的情况
if(!scanner.hasNext()) {
System.out.printf("[%s:%d]客户端下线!\n",clienSocket.getInetAddress(),clienSocket.getPort());
break;
}
//将从客户端接收过来的数据赋值到request中,为之后处理数据做铺垫
//这里的scanner.next()需要读到空白符才会停止,所以为了区分每一份的应用层的数据报,所以我们可以在客户端手动添加空白符,
//来控制每一份的应用层数据报
String request = scanner.next();
//2.计算响应,由于这是一个回显服务器,所以我们呢不需要对响应做处理,请求什么就返回什么响应
String response = process(request);
//3.将计算好的响应传回客户端(相当与将数据写回客户端)
outputStream.write(response.getBytes(),0,response.getBytes().length);
//4.打印服务器日志
System.out.printf("[%s:%d]res = %s,resp = %s\n",clienSocket.getInetAddress(),clienSocket.getPort(),
request,response);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
finally {
clienSocket.close();
}
}
private String process(String request) {
return request + "\n";
}
public static void main(String[] args) throws IOException {
tcpEchoServer server = new tcpEchoServer(9999);
server.start();
}
}
1.1.2.5实现一个TCP字典翻译服务器
java
public class tcpDictServer extends tcpEchoServer{
HashMap<String,String> map = null;
public tcpDictServer(int port) throws IOException {
super(port);
map = new HashMap<>();
map.put("server","服务\n");
map.put("client","客户端\n");
map.put("cat","🐱\n");
map.put("dog","🐕\n");
map.put("pig","🐖\n");
}
@Override
public String process(String request) {
return map.getOrDefault(request,"该词汇没有查询到");
}
public static void main(String[] args) throws IOException {
tcpDictServer tcpDictServer = new tcpDictServer(9999);
tcpDictServer.start();
}
}