转载请注明出处:http://blog.csdn.net/github_39430101/article/details/77753870
TCP通信
Socket简介
socket通常称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。其实就是客户端和服务端之间通信的一条管道。在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口应用于不同的服务。
应用程序通常通过”套接字”向网络发出请求或者应答网络请求。Socket和ServerSocket类库位于java.net包中。ServerSocket用于服务端,Socket是建立网络连接时使用的。在连接成功时,应用程序两端都会产生一个Socket实例,操作这个实例,完成所需的会话。
获取本地地址和端口号
java.net.Socket为套接字类,其提供了很多方法,其中我们可以通过Socket获取本地的地址以及端口号。
int getLocalPort() 该方法用于获取本地使用的端口号
InetAddress getLocalAddress() 该方法用于获取套接字绑定的本地地址
String getCanonicalHostName() 获取此IP地址的完全限定域名
String getHostAddress() 返回IP地址字符串(以文本表现形式)
Demo
public class TestSocket {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost",8080);
InetAddress add = socket.getLocalAddress();
System.out.println(add.getCanonicalHostName());
System.out.println(add.getHostAddress());
System.out.println(add.getLocalHost());
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取远端地址和端口号
Socket也提供了获取远端的地址以及端口号的方法
int getPort() 该方法用于获取远端使用的端口号
InetAddress.getInetAddress() 该方法用于获取套接字绑定的远端地址
Demo
public void testSocket() throws Exception{
Socket socket = new Socket("localhost",8088);
InetAddress inetAdd = socket.getInetAddress();
System.out.println(inetAdd.getCanonicalHostName());
System.out.println(inetAdd.getHostAddress());
System.out.println(socket.getPort());
}
获取网络输入流和网络输出流
通过Socket获取输入流与输出流,这两个方法是使用Socket通讯的关键方法。封装了TCP协议的Socket是基于流进行通讯的。所以我们在创建了双方连接后,只需要获取相应的输入与输出流即可。
InputStream getInputStream() 该方法用于返回此套接字的输入流
OutputStream getOutputStream() 该方法用于返回此套接字的输出流
Demo
public void testSocket() throws Exception {
Socket socket = new Socket("localhost",8088);
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
}
Socket通讯模型
Server端ServerSocket监听
java.net.ServerSocket是运行于服务端应用程序中。通常创建ServerSocket需要指定服务端口号,之后监听Socket的连接。监听方法为:
Socket accept() 该方法是一个阻塞方法
//创建ServerSocket并申请服务端口8088
ServerSocket server = new ServerSocket(8088);
//方法会产生阻塞,直到某个Socket连接,并返回请求连接的Socket
Socket socket = server.accept();
Client端Socket连接
Socket socket = new Socket("localhost",8088);
完整示例(单线程)
- 服务端创建ServerSocket
- 通过调用ServerSocket的accept方法监听客户端的连接
- 客户端创建Socket并指定服务端的地址以及端口来建立与服务端的连接
- 当服务端accept发现客户端连接后,获取对应客户端的Socket
- 双方通过Socket分别获取对应的输入与输出流进行数据通讯
- 通讯结束后关闭连接
/**************** Server端 *****************/
public class ServerDemo {
public static void main(String[] args) {
ServerSocket server = null;
try {
//创建ServerSocket并监听8088端口
server = new ServerSocket(8088);
//监听客户端的连接
System.out.println("----等待客户端的连接----");
Socket socket = server.accept();
//客户端连接后,通过该Socket与客户端交互
//获取输入流,用于读取客户端发送过来的消息
InputStream is = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//获取输出流,用于向客户端发送消息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os,"UTF-8"),true);
pw.write("我是服务端,我监听到你了哦");
pw.flush();
//读取客户端发送的消息
String message = reader.readLine();
System.out.println("客户端说:"+message);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(server != null) {
try {
server.close();
} catch (IOException e) {
}
}
}
}
}
/********** Client端 ************/
public class ClientDemo {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket("localhost",8088);
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
//获取输出流,用于向服务端发送消息
OutputStream os = socket.getOutputStream();
OutputStreamWriter ow = new OutputStreamWriter(os,"UTF-8");
PrintWriter pw = new PrintWriter(ow,true);
//向服务端发送一个字符串
pw.println("你好服务器");
pw.flush();
String message = br.readLine();
System.out.println("服务器说:"+message);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if(socket != null) {
socket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
先启动服务器,后连接。
Server端多线程模型
我们已经知道如何使用ServerSocket与Socket进行通讯了,但是只能点对点,一个服务端对一个客户端。若我们想让一个服务端可以同时支持多个客户端应该怎么做呢?这时我们需要分析之前的代码。我们可以看到,当服务端的ServerSocket通过accept方法监听到一个客户端连接后,就获取该Socket并与该客户端通过流进行双方的通讯了,这里的问题在于,只有不断的调用accept方法,我们才能监听到不同客户端的连接。但是若我们循环监听客户端的连接,又无暇顾及与连接上的客户端交互,这时我们需要做的事情就是并发。我们可以创建一个线程类ClientHandler,并将于客户端交互的工作全部委托线程来处理。这样我们就可以在当一个客户端连接后,启动一个线程与客户端交互,而我们也可以循环监听客户端的连接了。
/*************** Server端应用程序 **************/
package com.code.socket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerDemo {
public static void main(String[] args){
ServerSocket server = null;
try{
server = new ServerSocket(8088);
while(true){
//循环监听客户端的连接
Socket socket = server.accept();
//当一个客户端连接后,启动线程来处理该客户端的交互
new ClientHandler(socket).start();
}
} catch (Exception e){
e.printStackTrace();
} finally {
if(server != null){
try{
server.close();
} catch (IOException e){
}
}
}
}
}
/*
* 线程类 该线程的作用是并发与客户端进行交互
*/
class ClientHandler extends Thread{
private Socket socket;
public ClientHandler(Socket socket) {
this.socket = socket;
}
public void run() {
try {
//获取输入流,用于读取客户端发送过来的消息
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is,"UTF-8"));
String info = null;
while(null != (info=br.readLine())) {
System.out.println("客户端说:"+info);
}
//获取输出流,用于向客户端发送给消息
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(os,"UTF-8"),true);
pw.println("你好客户端");
pw.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
UDP通信
创建接受包
DatagramPacket:UDP数据报给予IP建立的,每台主机有65536个端口号可以使用。数据报中字节数限制为65536-8.包含8字节的头信息。
DatagramPacket(byte[] buf,int length)
将数据包中Length长的数据装进buf数组
DatagramPacket(byte[] buf,int offset,int length)
将数据包中从offset开始,length长的数据装进buf数组
创建发送包
DatagramPacket(byte[] buf,int length,InetAddress clientAddress,int clientPort)
从buf数组中,取出length长的数据创建爱你数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。
DatagramPacket(byte[] buf,int offset,int length,InetAddress clientAddress,int clientPort)
从Buf数组中,取出Offset开始的、Length长的数据创建数据包对象,目标是clientAddress地址,clientPort端口,通常用来发送数据给客户端。
DatagramSocket
服务端接收
DatagramSocket用于接收和发送UDP的Socket实例
DatagramSocket(int port)
创建实例,并固定监听port端口的报文。通常用于服务端。
其中方法:
receive(DatagramPacket d)
接收数据报文到d中。receive方法产生阻塞。会一直等待直到有数据被读取到。
客户端发发送
无参的构造方法DatagramSocket()通常用于客户端编程,它并没有特定监听的端口,仅仅使用一个临时的。程序会让操作体统分配一个可用的端口。
send(DatagramPacket dp)
该方法用于发送报文dp到目的地
代码如下:
/***************** Server端 *****************/
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UdpServer {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(8088);
byte[] data = new byte[1024];
DatagramPacket dp = new DatagramPacket(data,data.length);
socket.receive(dp);//会产生阻塞,读取发送过来的数据
String str = new String(dp.getData(),0,dp.getLength());//从包中取数据
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(socket != null) {
socket.close();
}
}
}
}
/*********************** 客户端 **********************/
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UdpClient {
public static void main(String[] args) {
DatagramSocket socket = null;
try {
socket = new DatagramSocket(); //创建socket
byte[] data = "你好".getBytes();
//创建爱你发送包
DatagramPacket dp = new DatagramPacket(data,data.length,InetAddress.getByName("localhost"),8088);
socket.send(dp); //发送数据
} catch (Exception e) {
e.printStackTrace();
} finally {
if(socket != null) {
socket.close();
}
}
}
}