对于网络编程其实就相当于,在生活中人与人之间的交流,提问的一方作为用户端,回答问题的一方作为服务器端
接下来我就和大家我学习socket网络编程所学到的知识
Tcp/Ip协议概述
Tcp/Ip协议的四层结构
每一层的用处
-
运用层:应用层为用户提供所需要的各种服务,负责传送各种最终形态的数据,是直接与用户打交道的层,典型协议包含HTTP、FTP等
-
传输层:传输层为应用层实体提供端到端的通信功能,该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP)。其中,TCP协议提供的是一种可靠的、面向连接的数据传输服务;而UDP协议提供的是不可靠的、无连接的数据传输服务。
-
网络层: 网络层主要解决主机到主机的通信问题。该层有四个主要协议:网络协议(IP)、地址解析协议(ARP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。其中,IP协议是网络层最重要的协议。
-
链路层:链路层负责建立电路连接,是整个网络的物理基础,典型的协议包括以太网、ADSL等。
进行网络编程需要解决的两个问题:
-
如何在网络中找到一台或多台主机:
可以依靠网络层的IP解决,即提供主机的IP地址找到主机。
-
当通信双方成功连接后,如何进行可靠的数据传输:
针对传输层进行编程,传输层主要的两个协议是TCP和UDP。
TCP和UDP的区别
- TCP(Tranfer Control Protocol)
面向连接的,可靠的传输协议
- UDP(User Datagram Protocol)
无连接的,不可靠的传输协议
UDP编程代码:
服务器端
package 网络编程.Day_04_22_Code;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
public class UDPServer {
public static void main(String[] args) throws IOException {
// 创建数据包(数据报) socket对象,绑定端口 4722 用来通信(接收)
DatagramSocket receive_socket = new DatagramSocket(4722);
// 准备一个返回数据的缓冲区
byte buf[] = new byte[1000];
// 创建数据报(数据包),准备好接收 socket对象获取到的数据信息
DatagramPacket receive_packet = new DatagramPacket(buf, buf.length);
String str="我收到了消息";
while ( true ) {
// 使用 socket对象 接收 packet内容 放到服务器
receive_socket.receive(receive_packet);
// 获取主机IP
String name = receive_packet.getAddress().toString();
System.out.println(" 来自:" + name + " 端口:" + receive_packet.getPort());
// 获取数据内容
System.out.println(receive_packet.getLength());
String data = new String(receive_packet.getData(),0,receive_packet.getLength());
System.out.println(" 接收到的数据:" + data);
buf=str.getBytes();
data = new String(buf,0,buf.length);
// 创建数据报(数据包),封装需要发送的数据内容(数据长度、服务器地址+端口...)
DatagramPacket send_packet = new DatagramPacket(buf, buf.length,InetAddress.getByName("127.0.0.1"),4711);
// 使用 socket对象 发送 packet内容 到服务器
receive_socket.send(send_packet);
}
}
}
客户端
package 网络编程.Day_04_22_Code;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
public class UDPClient {
public static void main(String[] args) throws IOException {
// 创建数据包(数据报) socket对象,绑定端口 4711 用来通信(发送)
DatagramSocket send_socket = new DatagramSocket(4711);
// 准备好需要发送的数据
// String msg = "qwerDF1234567";
String msg = "我是客户端向服务端发送一条消息此处凑够二十个字";
byte[] databyte = new byte[1000];
databyte = msg.getBytes();
String data = new String(databyte,0,databyte.length);
System.out.println(data);
// System.out.println(databyte.length);
// 创建数据报(数据包),封装需要发送的数据内容(数据长度、服务器地址+端口...)
DatagramPacket send_packet = new DatagramPacket(databyte, databyte.length,InetAddress.getByName("127.0.0.1"),4722);
// 使用 socket对象 发送 packet内容 到服务器
send_socket.send(send_packet);
// 准备一个返回数据的缓冲区
byte buf[] = new byte[1000];
// 创建数据报(数据包),准备好接收 socket对象获取到的数据信息
DatagramPacket receive_packet = new DatagramPacket(buf, buf.length);
// 使用 socket对象 接收 packet内容 放到服务器
send_socket.receive(receive_packet);
// 获取主机IP
String name = receive_packet.getAddress().toString();
System.out.println(" 来自:" + name + " 端口:" + receive_packet.getPort());
// 获取数据内容
System.out.println(receive_packet.getLength());
data = new String(receive_packet.getData(),0,receive_packet.getLength());
System.out.println(" 接收到的数据:" + data);
}
}
在控制台的结果
TCP编程代码:
package 网络编程.Day_04_22_Code;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class HttpURLConnectionTest {
public static void main(String[] args) throws IOException {
URL serverURL = new URL("https://www.baidu.com/");
HttpsURLConnection con = (HttpsURLConnection)serverURL.openConnection();
con.setDoOutput(true);
OutputStream os = con.getOutputStream();
int code = con.getResponseCode();
if ( code == HttpsURLConnection.HTTP_OK) {
InputStream is = con.getInputStream();
FileOutputStream fos = new FileOutputStream("testbaidu.html");
byte[] buf = new byte[1024];
int c = 0;
while ( ( c = is.read(buf,0,buf.length)) != -1 ) {
fos.write(buf, 0, c);
}
fos.close();
is.close();
}
}
}
最后会在根目录下生成一个文件
Socket 编程
Socket通常称作“套接字”,通常通过“套接字”向网络发出请求或者应答网络请求
套接字使用TCP提供了两台计算机之间的通信机制。 客户端程序创建一个套接字,并尝试连接服务器的套接字。
当连接建立时,服务器会创建一个 Socket 对象。客户端和服务器现在可以通过对 Socket 对象的写入和读取来进行通信。
java.net.Socket 类代表一个套接字,并且 java.net.ServerSocket 类为服务器程序提供了一种来监听客户端,并与他们建立连接的机制。
以下步骤在两台计算机之间使用套接字建立TCP连接时会出现:
-
服务器实例化一个 ServerSocket 对象,表示通过服务器上的端口通信。
-
服务器调用 ServerSocket 类的 accept() 方法,该方法将一直等待,直到客户端连接到服务器上给定的端口。
-
服务器正在等待时,一个客户端实例化一个 Socket 对象,指定服务器名称和端口号来请求连接。
-
Socket 类的构造函数试图将客户端连接到指定的服务器和端口号。如果通信被建立,则在客户端创建一个 Socket 对象能够与服务器进行通信。
-
在服务器端,accept() 方法返回服务器上一个新的 socket 引用,该 socket 连接到客户端的 socket。
连接建立后,通过使用 I/O 流在进行通信,每一个socket都有一个输出流和一个输入流,客户端的输出流连接到服务器端的输入流,而客户端的输入流连接到服务器端的输出流。
TCP 是一个双向的通信协议,因此数据可以通过两个数据流在同一时间发送.以下是一些类提供的一套完整的有用的方法来实现 socket。
ServerSocket 类的方法
服务器应用程序通过使用 java.net.ServerSocket 类以获取一个端口,并且侦听客户端请求。
ServerSocket 类有四个构造方法:
创建非绑定服务器套接字。 如果 ServerSocket 构造方法没有抛出异常,就意味着你的应用程序已经成功绑定到指定的端口,并且侦听客户端请求。
这里有一些 ServerSocket 类的常用方法:
Socket 类的方法
java.net.Socket 类代表客户端和服务器都用来互相沟通的套接字。客户端要获取一个 Socket 对象通过实例化 ,而 服务器获得一个 Socket 对象则通过 accept() 方法的返回值。
Socket 类有五个构造方法:
当 Socket 构造方法返回,并没有简单的实例化了一个 Socket 对象,它实际上会尝试连接到指定的服务器和端口。
下面列出了一些感兴趣的方法,注意客户端和服务器端都有一个 Socket 对象,所以无论客户端还是服务端都能够调用这些方法。
InetAddress 类的方法
这个类表示互联网协议(IP)地址。下面列出了 Socket 编程时比较有用的方法:
单客户Socket编程
服务器端:
package 网络编程.资料.single;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
public static void main(String[] args) {
try {
ServerSocket server = null;
try {
// 创建ServerSocket对象,指定端口是4700
server = new ServerSocket(4700);
System.out.println("服务器启动成功");
} catch (Exception e) {
System.out.println("服务器启动出错");
}
Socket socket = null;
try {
// 调用ServerSocket的accept方法,可以接受客户端的请求,并返回当前的Socket对象
socket = server.accept();
} catch (Exception e) {
e.printStackTrace();
}
String line;
// 获得基于Socket的输入流
BufferedReader is = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
// 获得基于Socket的输出流
PrintWriter os = new PrintWriter(socket.getOutputStream());
// 获得控制台输入
BufferedReader sin = new BufferedReader(new InputStreamReader(
System.in));
// 输出客户端输入
System.out.println("Client:" + is.readLine());
line = sin.readLine();
// 向客户端写数据
while (!line.equals("exit")) {
os.println(line);
os.flush();
System.out.println("Client:" + is.readLine());
line = sin.readLine();
}
// 资源关闭操作
os.close();
is.close();
socket.close();
server.close();
} catch (Exception e) {
System.out.println("Error:" + e);
}
}
}
客户端:
package 网络编程.资料.single;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class TCPClient {
public static void main(String[] args) {
try {
// 创建Socket,指定ip,port
Socket socket = new Socket("127.0.0.1", 4700);
// 获得键盘输入
BufferedReader sin = new BufferedReader(new InputStreamReader(
System.in));
// 获得基于Socket的输入流和输出流
PrintWriter os = new PrintWriter(socket.getOutputStream());
BufferedReader is = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String readline;
readline = sin.readLine();
// 向服务器写数据
while (!readline.equals("exit")) {
os.println(readline);
os.flush();
System.out.println("Server:" + is.readLine());
readline = sin.readLine();
}
os.close();
is.close();
socket.close();
} catch (Exception e) {
System.out.println(e);
}
}
}
注意先启动服务器再启动客户端
这种单客户的交流也是有限制的,必须客户端一句,服务器一句,当然也可以改善,把while循环改改就可以
模拟多人群聊(这里服务器只是起到了一个中间转发的过程)
服务器端的代码:
MultiThreadServer的代码
//MultiThreadServer的代码
package 网络编程.Day_04_21_Code;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
public class MultiThreadServer implements Runnable {
private Socket socket;
//定义输出输入流
DataInputStream is;
DataOutputStream os;
public MultiThreadServer( Socket s ) {
this.socket = s;
}
@Override
public void run() {
String line;
try {
// 获取socket对象的输入输出流
is = new DataInputStream( socket.getInputStream() );
os = new DataOutputStream( socket.getOutputStream() );
while ( true ) {
//将客户端发送到服务器的内容存到line中
line = is.readUTF();
// 在服务器端打印出客socket的hashcode的值和line
System.out.println(" 客户端 [ " + socket.hashCode() + " ]: " + line);
// 调用定义好的方法将内容转发给全部的客户端
TCPServer_02.sendtoAll(" 客户端 [ " + socket.hashCode() + " ]: " + line);
if ( line.equals("exit") ) {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
TCPServer_02的代码
//TCPServer_02的代码
package 网络编程.Day_04_21_Code;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TCPServer_02 {
//定义一个静态的泛型为MultiThreadServer的线程安全的arraylist
static List<MultiThreadServer> clients = Collections.synchronizedList( new ArrayList<MultiThreadServer>() );
//定义向全部的客户转发消息的方法
static void sendtoAll( String msg ) {
//便利所哟逇服务
for ( int i = 0; i < clients.size(); i++ ) {
MultiThreadServer server = clients.get(i);
try {
//将转发的消息转发
server.os.writeUTF(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception {
//创建ServerSocket对象
ServerSocket server_socket = null;
try {
//指定端口
server_socket = new ServerSocket(5700);
System.out.println(" 服务器启动成功!!!");
} catch (Exception e) {
System.out.println(" 服务器启动失败!!!");
}
Socket socket = null;
while ( true ) {
//创建socket 实例
socket = server_socket.accept();
//创建一个 MultiThreadServer类,将本次的socket作为构造参数传入
MultiThreadServer st = new MultiThreadServer(socket);
// 创建一个线程
Thread t = new Thread(st);
// 将该服务存放到线程安全的arraylist中
clients.add(st);
// 启动线程
t.start();
}
}
}
客户端的代码:
package 网络编程.Day_04_21_Code;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;
public class TCPClient_02 {
public static void main(String[] args) throws UnknownHostException, IOException {
//创建 Socket 关键字,确定 IP 和 端口号
Socket socket = new Socket("127.0.0.1", 5700);
//获取键盘输入
BufferedReader sin = new BufferedReader( new InputStreamReader( System.in ) );
//定义socket的输出和输出流
DataOutputStream os = new DataOutputStream( socket.getOutputStream());
final DataInputStream is = new DataInputStream( socket.getInputStream());
// 定义字符串
String readline;
//用runnable接口创建线程
Runnable runnable = new Runnable() {
@Override
public void run() {
while ( true ) {
try {
//将服务器转发的信息赋值给 readline
String readline = is.readUTF();
//输出转发的信息
System.out.println(readline);
if ( readline.equals("exit")) {
break;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
//启动线程
new Thread(runnable).start();
while ( true ) {
//将键盘的输入赋值给readline
readline = sin.readLine();
// 向服务端发送数据
os.writeUTF(readline);
}
}
}
我启动了一个服务器端,三个客户端
客户端1:
客户端2
客户端3