Java网络编程(TCP)

时间:2022-08-27 20:18:46

Java网络编程(TCP)

网络编程就是两个或多个设备之间的数据交换,其实更具体的说,网络编程就是两个或多个程序之间的数据交换,和普通的单机程序相比,网络程序最大的不同就是需要交换数据的程序运行在不同的计算机上,这样就造成了数据交换的复杂。虽然通过IP地址和端口可以找到网络上运行的一个程序,但是如果需要进行网络编程,则还需要了解网络通讯的过程。

网络编程分为 客户端服务器(C/S)模式和浏览器服务器(B/S)模式,而网络编程遵循的协议分为:TCP和UDP。其实网络编程并不难,基本思路是首先客户端和服务器端要建立连接,然后客户端和服务器端进行数据交换,最后客户端和服务器端都断开连接释放资源。

1、简单的客户端程序:

客户端程序是整个连接的发起者,其具体实现步骤如下:

(1)、建立连接

Socket socket1=new Socket(serverIP, port);

或者可以直接使用域名创建Socket

Socket socket2 = new Socket(“www.sohu.com”,80);

根据建立的连接获得输入输出流

 OutputStream os = socket1.getOutputStream();    // 获得输出流

InputStream is = socket1.getInputStream(); // 获得输入流

然后对网络数据的操作就如同操作 I/O 流一样

(2)、发送数据

os.write(data.getBytes());

(3)、接收客户端反馈回的结果

byte[] b=new byte[1024];
int n=is.read(b);
String result=new String(b,0,n);

(4)、关闭流和连接

os.close();
is.close();
socket1.close();

一个简单客户端示例代码如下:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class SimpleSocketClient {

public static void main(String[] args) {
// TODO Auto-generated method stub
Socket socket1=null;
InputStream is=null;
OutputStream os=null;
String serverIP="127.0.0.1";
int port=10000;
String data="Hello";

try{
// 建立连接
socket1=new Socket(serverIP, port);

// 发送数据
os=socket1.getOutputStream();
os.write(data.getBytes());

// 接受数据
is=socket1.getInputStream();
byte[] b=new byte[1024];
int n=is.read(b);

// 输出反馈数据
System.out.println("服务器反馈:"+new String(b,0,n));

}catch(Exception e){
e.printStackTrace(); //打印异常信息
}finally {
try{
// 关闭流和连接
is.close();
os.close();
socket1.close();
}catch(Exception e2){
e2.printStackTrace();
}
}

}

}

2、简单的服务器端程序

服务器端是等待客户端的连接,然后接受客户端的请求,并对请求数据进行逻辑分析,然后将分析后的结果反馈给客户端,其具体实现步骤如下:

(1)、创建 ServerSocket对象,监听特定端口,等待客户端的连接

ServerSocket serverSocket=new ServerSocket(port);
Socket socket2=serverSocket.accept();

根据创建的连接创建输入输出流

OutputStream os = socket2.getOutputStream();    // 获得输出流

InputStream is = socket2.getInputStream(); // 获得输入流

(2)、接收客户端发送的内容

byte[] b=new byte[1024];
int n=is.read(b);
String quite=new String(b,0,n);

(3)、将处理的结果返回给客户端

os.write(result.getBytes());

(4)、关闭流和连接

os.close();
is.close();
socket2.close();
serverSocket.close();

一个简单的服务器端示例代码如下:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleSocketServer {

public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket=null;
Socket socket2=null;
OutputStream os=null;
InputStream is=null;
int port=10000;

try{
// 建立连接
serverSocket=new ServerSocket(port);

// 获得连接
socket2=serverSocket.accept();

// 接受客户端发送的内容
is=socket2.getInputStream();
byte[] b=new byte[1024];
int n=is.read(b);

// 输出
System.out.println("客户端发送内容为:"+new String(b,0,n));

// 向客户端发送回馈内容
os=socket2.getOutputStream();
os.write(b,0,n);
}catch(Exception e){
e.printStackTrace();
}finally {
try{
// 关闭流和链接
os.close();
is.close();
socket2.close();
serverSocket.close();

}catch(Exception e){
e.printStackTrace();
}
}

}

}

3、如何服用Socket连接?

在前面的示例中,客户端中建立了一次连接,只发送一次数据就关闭了,这就相当于拨打电话时,电话打通了只对话一次就关闭了,其实更加常用的应该是拨通一次电话以后多次对话,这就是复用客户端连接。那 么如何实现建立一次连接,进行多次数据交换呢?其实很简单,建立连接以后,将数据交换的逻辑写到一个循环中就可以了。这样只要循环不结束则连接就不会被关 闭。按照这种思路,可以改造一下上面的代码,让该程序可以在建立连接一次以后,发送三次数据,当然这里的次数也可以是多次,示例代码如下:

客户端代码:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class SimpleSocketClient {

public static void main(String[] args) {
// TODO Auto-generated method stub
Socket socket1=null;
InputStream is=null;
OutputStream os=null;
String serverIP="127.0.0.1";
int port=10000;
String data[] ={"First","Second","Third"};

try{
// 建立连接
socket1=new Socket(serverIP, port);

// 发送数据
os=socket1.getOutputStream();


// 接受数据
is=socket1.getInputStream();
byte[] b=new byte[1024];
for(int i=0;i<data.length;i++)
{
// 发送数据
os.write(data[i].getBytes());
// 接受数据
int n=is.read(b);
// 输出反馈数据
System.out.println("服务器反馈:"+new String(b,0,n));
}

}catch(Exception e){
e.printStackTrace(); //打印异常信息
}finally {
try{
// 关闭流和连接
is.close();
os.close();
socket1.close();
}catch(Exception e2){
e2.printStackTrace();
}
}

}
}

当然服务器端程序也要做相应修改,否则,服务器端程序进行一次数据交换后就关闭了,当客户端程序再次进行连接时就会报错,做相应修改之后的服务器段程序代码如下:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleSocketServer {

public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket=null;
Socket socket2=null;
OutputStream os=null;
InputStream is=null;
int port=10000;

try{
// 建立连接
serverSocket=new ServerSocket(port);

// 获得连接
socket2=serverSocket.accept();

// 接受客户端发送的内容
is=socket2.getInputStream();
os=socket2.getOutputStream();
byte[] b=new byte[1024];

for(int i=0;i<3;i++){
int n=is.read(b);
// 输出
System.out.println("客户端发送内容为:"+new String(b,0,n));

// 向客户端发送回馈内容
os.write(b,0,n);
}

}catch(Exception e){
e.printStackTrace();
}finally {
try{
// 关闭流和链接
os.close();
is.close();
socket2.close();
serverSocket.close();

}catch(Exception e){
e.printStackTrace();
}
}

}

}

运行之后输出结果为:
服务器端:

客户端发送内容为:First
客户端发送内容为:Second
客户端发送内容为:Third

客户端:

服务器反馈:First
服务器反馈:Second
服务器反馈:Third

4、如何使服务器端支持多个客户端同时工作呢?

前面介绍的服务器端程序,只是实现了概念上的服务器端,离实际的服务器端程序结构距离还很遥远,如果需要让服务器端能够实际使用,那么最需要解决的问题就是——如何支持多个客户端同时工作。

一个服务器端一般都需要同时为多个客户端提供通讯,如果需要同时支持多个客户端,则必须使用线程的概念。简单来说,也就是当服务器端接收到一个连接时,启动一个专门的线程处理和该客户端的通讯。

按照这个思路改写的服务端示例程序将由两个部分组成,MulThreadSocketServer类实现服务器端控制,实现接收客户端连接,然后开启专门的逻辑线程处理该连接,LogicThread类实现对于一个客户端连接的逻辑处理,将处理的逻辑放置在该类的run方法中。该示例的代码实现为:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
* 支持多个客户端同时工作
*/

public class MulThreadSocketServer {

public static void main(String[] args) {
// TODO Auto-generated method stub
ServerSocket serverSocket=null;
Socket socket3=null;
int port=10000;

try{
// 建立连接
serverSocket=new ServerSocket(port);
System.out.println("服务器已启动:");
while(true){
// 获得连接
socket3=serverSocket.accept();
// 启动线程
(new LogicThread(socket3)).start();

}
}catch(Exception e){
e.printStackTrace();
}finally {
try{
serverSocket.close();
}catch(Exception e1){
e1.printStackTrace();
}
}

}
}

class LogicThread extends Thread{
private Socket socket;
private InputStream is;
private OutputStream os;
public LogicThread(Socket skt) {
this.socket=skt;
}

public void run() {
byte[] b=new byte[1024];
try{
// 初始化流
os=socket.getOutputStream();
is=socket.getInputStream();
for(int i=0;i<3;i++){
// 读取数据
int n=is.read(b);
// 打印客户端请求
System.out.println("客户端请求为:"+new String(b,0,n));
// 逻辑处理
byte[] response=logic(b,0,n);
// 反馈数据
os.write(response);
}
}catch(Exception e){
e.printStackTrace();
}finally {
try{
os.close();
is.close();
socket.close();
}catch(Exception e2){
e2.printStackTrace();
}
}
}

private byte[] logic(byte[] b, int off, int len) {

byte[] response=new byte[len];
// 将有效数据拷贝到数据response中
System.arraycopy(b, 0, response, 0, len);
return response;
}
}

为了演示这个实例,需要多个客户端来同时连接该服务器,在客户端程序中启动多个线程,每个线程模拟一个实际的客户端,示例代码如下:

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class MulThreadSocket {

public static void main(String[] args) {

String serverIP="127.0.0.1";
int port=10000;

int i=0;
for(i=0;i<3;i++){
System.out.println("启动第"+i+"个客户端");
try{
// 启动一个新的线程来创建一个新的客户端
Thread thread=new Thread(new Runnable() {
Socket socket=null;
InputStream is=null;
OutputStream os=null;
String [] data=new String[]{"First","Second","Third"};
public void run() {
try{
socket=new Socket(serverIP, port);
is=socket.getInputStream();
os=socket.getOutputStream();
byte[] b=new byte[1024];
for(int j=0;j<3;j++){
// 向服务器发送请求
os.write(data[j].getBytes());
// 获得服务器返回的数据
int n=is.read(b);
// 打印服务器的回馈
System.out.println("数据库反馈为:"+new String(b,0,n));
}
}catch(Exception e0){
e0.printStackTrace();
}finally {
try{
os.close();
is.close();
socket.close();
}catch(Exception e2){
e2.printStackTrace();
}
}
}
});
thread.start();


}catch(Exception e1){
e1.printStackTrace();
}
}

}

}

其中每次启动新的线程是利用匿名内部了实现的。
代码运行结果如下:
客户端:

启动第0个客户端
启动第1个客户端
启动第2个客户端
数据库反馈为:First
数据库反馈为:First
数据库反馈为:Second
数据库反馈为:Third
数据库反馈为:Second
数据库反馈为:Third
数据库反馈为:First
数据库反馈为:Second
数据库反馈为:Third

服务器端:

服务器已启动:
客户端请求为:First
客户端请求为:First
客户端请求为:Second
客户端请求为:Third
客户端请求为:Second
客户端请求为:Third
客户端请求为:First
客户端请求为:Second
客户端请求为:Third

从上面的结果可以看出,主线程启动第一个客户端线程后,紧接着又启动了第2,3个客户端线程,这个过程很快,而每个客户端线程创建socket连接,和数据交换是比较耗时的。后面的结果可以看出三个客户端以及其对应的三个服务器端程序是交替抢占CPU的。

关于基础的TCP方式的网络编程就介绍这么多,UDP方式的网络编程在Java语言中的实现可以参看另一篇文章。
http://blog.csdn.net/xiaolangfanhua/article/details/52769386