网络编程中有两个主要的问题:
(1)如何准确的定位网络上一台或多台主机(IP和端口号)
- IP地址是唯一标识网络上的一台计算机(通信实体),网络中的www.baidu.com是域名,对应着某一个IP地址。
- 在Java中使用InetAddress类代表IP。
- 本地回环地址:127.0.0.1或者localhost。
- 端口号是标识正在计算机上运行的进程(程序)
- 不同的进程有不同的端口号,用16位的二进制表示,即0-65535之间。常见的有tomcat的8080端口,MysqL的3306端口。
- 最后,IP地址和端口号的组合,叫做网络套接字(Socket)。进程之间的通信需要借助IP地址和端口号,即网络套接字(Socket)
(2)找到主机后如何可靠高效地进行数据传输(网络通信协议)
传输层协议
(1)TCP协议(传输控制协议,Transmission Control Protocol)
- 使用TCP协议前,须先建立TCP连接,形成数据通道
- 传输前,采用“三次握手”方式,是可靠的
- TCP协议进行通信的两个应用进程:客户端,服务器
- 在连接中可进行大数据量的传输
- 传输完毕,需释放已建立的连接,效率低
(2)UDP协议(用户数据报协议,User Datagram Protocol)
- 将数据,源,目的封装成数据报,不需要建立连接
- 每个数据报的大小限制在64K内
- 因无需连接,故是不可靠的
- 发送数据结束时,无需释放资源,速度快
基于TCP的网络编程
(1)客户端Socket的工作过程包含以下四个基本的步骤:
- 创建Socket:根据指定服务器端的IP地址或端口号构造Socket类对象。若服务器端响应,则建立客户端到服务器的通信线路;若连接失败,会出现异常。
- 打开连接到Socket的输入/输出流:使用getInputStream()方法获得输入流,使用getoutpurStream()方法获得输出流,进行数据传输。
- 按照一定的协议对Socket进行读/写操作:通过输入流读取服务器端返回的信息,通过输出流写入线程。
- 关闭Socket:断开客户端到服务器的连接,释放线路。
(2)服务器端ServerSocket的工作过程包含以下四个基本的步骤:
- 调用ServerSocket(int port):创建一个服务器端套接字,并绑定到指定端口上。用于监听客户端的请求。
- 调用accept():监听连接请求,如果客户端请求连接,则接受连接,返回通信套接字对象。
- 调用该Socket类对象的getoutputStream()和getInputStream():获取输出流和输入流,开始网络数据的发送和接收。
- 关闭ServerSocket和Socket的对象:客户端访问结束,关闭通信套接字。
ServerSocket对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员,也就是说,服务器必须事先建立一个等待客户请求建立套接字连接的ServerSocket对象。
Socket对象是客户端通信的节点,创建的同时会自动向服务器发起连接。
源码
客户端:
package cn.csu.demo;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.socket;
import java.net.UnkNownHostException;
public class Client {
public static void main(String[] args) {
Socket socket=null;
OutputStream os=null;
try {
//1.创建一个Socket对象,通过构造器指明服务器端的IP地址,以及其接收程序的端口号
socket = new Socket(InetAddress.getByName("localhost"),9090);
//2.getoutputStream():发送数据
os = socket.getoutputStream();
//3.具体的输出过程
os.write("我是客户端,请多关照".getBytes());
} catch (IOException e) {
e.printstacktrace();
}finally{
//4.关闭相应的流和Socket
if(os!=null){
try {
os.close();
} catch (IOException e) {
e.printstacktrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
}
}
服务器端:
package cn.csu.demo;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.socket;
public class Server {
public static void main(String[] args) {
ServerSocket serversocket=null;
Socket socket=null;
InputStream is=null;
try {
//1.创建一个ServerSocket的对象,通过构造器指明自身的端口号
serversocket = new ServerSocket(9090);
//2.调用其accept()方法,返回一个Socket对象
socket = serversocket.accept();
//3.调用Socket对象的getInputStream()方法,获取一个从客户端发送过来的输入流
is = socket.getInputStream();
//4.对获取的输入流进行的操作
byte [] b=new byte[20];
int len;
while((len=is.read(b))!=-1){
String str=new String(b,0,len);
System.out.println(str);
}
} catch (IOException e) {
// Todo Auto-generated catch block
e.printstacktrace();
}finally{
//关闭相应的流以及Socket,ServerSocket的对象
if(is!=null){
try {
is.close();
} catch (IOException e) {
e.printstacktrace();
}
}
if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printstacktrace();
}
}
if(serversocket!=null){
try {
serversocket.close();
} catch (IOException e) {
e.printstacktrace();
}
}
}
}
}
基于UDP的网络编程
- 类DatagramSocket和DatagramPacket实现了基于UDP协议的网络编程。
- UDP数据报通过数据报套接字DatagramSocket发送和接收,系统不能保证UDP数据报一定能够安全送到目的地,也不能确定是吗时候可以抵达。
- DatagramPacket对象封装了UDP数据报,在数据报中包含了发送端的IP地址和端口号,以及接收端的IP地址和端口号。
- UDP协议中每个数据报都给出了完整的地址信息,因此无需建立发送方和接收方的连接。
工作流程:(发送端与接收端流程一样)
源码
客户端(发送端):
package cn.csu.udp;
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 Client {
public static void main(String[] args) {
DatagramSocket ds=null;
try {
ds = new DatagramSocket();
byte [] b="你好,我是要发送的数据".getBytes();
//创建一个数据报,每一个数据报不能大于64K,都记录着数据信息,发送端的IP,端口号,以及要发送到的接收端的IP,端口号
DatagramPacket dp=new DatagramPacket(b, 0, b.length, InetAddress.getByName("localhost"), 9090);
ds.send(dp);
}catch (IOException e) {
e.printstacktrace();
}finally{
if(ds!=null){
ds.close();
}
}
}
}
服务器端(接收端):
package cn.csu.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.socketException;
public class Server {
public static void main(String[] args) {
DatagramSocket ds=null;
try {
ds=new DatagramSocket(9090);
byte [] container=new byte[1024];
DatagramPacket dp=new DatagramPacket(container, 0, container.length);
ds.receive(dp);
String str=new String(dp.getData(),0,dp.getLength());
System.out.println(str);
} catch (SocketException e) {
e.printstacktrace();
} catch (IOException e) {
e.printstacktrace();
}finally{
if(ds!=null){
ds.close();
}
}
}
}
基于TCP协议的多线程服务器
服务端:
package com.csu.marden;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.socket;
public class Tcpserver {
public static void main(String[] args) throws IOException {
//创建socket,绑定到65000端口
ServerSocket serverSocket=new ServerSocket(65000);
//循环用以监听
while (true){//这是多线程的,通过循环创建多个socket来实现
//监听65000端口,直到有客户端信息发过来
Socket socket=serverSocket.accept();
//执行相关操作
new CaluteLength(socket).start();
}
}
}
线程类:
package com.csu.marden;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.socket;
public class CaluteLength extends Thread{
//以Socket为成员变量
private Socket socket;
public CaluteLength(Socket socket){this.socket=socket;}
@Override
public void run(){
try {
//获取输入流
InputStream inputStream=socket.getInputStream();
int ch=0;
byte[] buff=new byte[1024];
//buff用来读取输入的内容,ch用来获取数组长度
ch=inputStream.read(buff);
String content=new String(buff,0,ch);//把字节流转为字符串
System.out.println(content);
} catch (IOException e) {
e.printstacktrace();
}
}
}
客户端1:
package com.csu.marden;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.socket;
import java.util.Random;
public class TCPClient1 {
public static void main(String[] args) throws IOException {
//转为byte数组,写到输入流中
for(int i=0;i<100;i++){
//创建Socket,并指定连接的是本机的端口号为65000的服务器socket
Socket socket=new Socket("127.0.0.1",65000);
// 获取输出流
OutputStream outputStream=socket.getoutputStream();
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMnopQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int j=0;j<6;j++){
int number=random.nextInt(62);
sb.append(str.charat(number));
}
outputStream.write(new String("客户端1"+":"+"socket"+i+":"+sb.toString()).getBytes());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Todo Auto-generated catch block
e.printstacktrace();
}
outputStream.close();
socket.close();
}
}
}
客户端2:
package com.csu.marden;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.socket;
import java.util.Random;
public class TCPClient2 {
public static void main(String[] args) throws IOException {
//转为byte数组,写到输入流中
for(int i=0;i<100;i++){
//创建Socket,并指定连接的是本机的端口号为65000的服务器socket
Socket socket=new Socket("127.0.0.1",65000);
// 获取输出流
OutputStream outputStream=socket.getoutputStream();
String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMnopQRSTUVWXYZ0123456789";
Random random=new Random();
StringBuffer sb=new StringBuffer();
for(int j=0;j<6;j++){
int number=random.nextInt(62);
sb.append(str.charat(number));
}
outputStream.write(new String("客户端2"+":"+"socket"+i+":"+sb.toString()).getBytes());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// Todo Auto-generated catch block
e.printstacktrace();
}
outputStream.close();
socket.close();
}
}
}