网络编程重在理论知识,我就直接转载大神笔记了,再加上课上的一些知识点和代码。希望对学习网络编程有所帮助。
通过网络发送数据是一项复杂的操作,必须仔细地协调网络的物理特性以及所发送数据的逻辑特征。通过网络将数据从一台主机发送到另外的主机,这个过程是通过计算机网络通信来完成。
网络通信的不同方面被分解为多个层,层与层之间用接口连接。通信的双方具有相同的层次,层次实现的功能由协议数据单元(PDU)来描述。不同系统中的同一层构成对等层,对等层之间通过对等层协议进行通信,理解批次定义好的规则和约定。每一层表示为物理硬件(即线缆和电流)与所传输信息之间的不同抽象层次。在理论上,每一层只与紧挨其上和其下的层对话。将网络分层,这样就可以修改甚至替换某一层的软件,只要层与层之间的接口保持不变,就不会影响到其他层。
计算机网络体系结构是计算机网络层次和协议的集合,网络体系结构对计算机网络实现的功能,以及网络协议、层次、接口和服务进行了描述,但并不涉及具体的实现。接口是同一节点内相邻层之间交换信息的连接处,也叫服务访问点(SAP)。
世界上第一个网络体系结构由IBM公司提出(1974年,SNA),以后其他公司也相继提出自己的网络体系结构。为了促进计算机网络的发展,国际标准化组织ISO在现有网络的基础上,提出了不基于具体机型、操作系统或公司的网络体系结构,称为开放系统互连参考模型,即OSI/RM(Open System Interconnection Reference Model)。
ISO制定的OSI参考模型过于庞大、复杂招致了许多批评。与此相对,美国国防部提出了TCP/IP协议栈参考模型,简化了OSI参考模型,由于TCP/IP协议栈的简单,获得了广泛的应用,并成为后续因特网使用的参考模型。
这里首先介绍OSI参考模型。OSI模型把网络通信的工作分为7层,分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
1、TCP/IP,即Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议,是Internet最基本的协议,Internet国际互联网络的基础。
2、TCP/IP协议是一个开放的网络协议簇,它的名字主要取自最重要的网络层IP协议和传输层TCP协议。TCP/IP协议定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。TCP/IP参考模型采用4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求,这4个层次分别是:网络接口层、网络层(IP层)、传输层(TCP层)、应用层。
1、如同人与人之间相互交流是需要遵循一定的规则(如语言)一样,计算机之间能够进行相互通信是因为它们都共同遵守一定的规则,即网络协议。
2、OSI参考模型和TCP/IP模型在不同的层次中有许多不同的网络协议,如图所示:
网络协议之间的关系图如下:
IP协议的作用在于把各种数据包准备无误的传递给对方,其中两个重要的条件是IP地址和MAC地址。由于IP地址是稀有资源,不可能每个人都拥有一个IP地址,所以我们通常的IP地址是路由器给我们生成的IP地址,路由器里面会记录我们的MAC地址。而MAC地址是全球唯一的。举例,IP地址就如同是我们居住小区的地址,而MAC地址就是我们住的那栋楼那个房间那个人。IP地址采用的IPv4格式,目前正在向IPv6过渡。
TCP(传输控制协议)是面向连接的传输层协议。TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。TCP协议采用字节流传输数据。
TCP报文段包括协议首部和数据两部分,协议首部的固定部分是20个字节,首部的固定部分后面是选项部分。
下面是报文段首部各个字段的含义:
TCP是面向连接的协议,因此每个TCP连接都有3个阶段:连接建立、数据传送和连接释放。连接建立经历三个步骤,通常称为“三次握手”。
TCP三次握手过程如下:
UDP,用户数据报协议,它是TCP/IP协议簇中无连接的运输层协议。
UDP协议由两部分组成:首部和数据。其中,首部仅有8个字节,包括源端口和目的端口、长度(UDP用于数据报的长度)、校验和。
//创建DatagramSocket对象,绑定端口3456
DatagramSocket sendSocket = new DatagramSocket(3456);
//准备好要发送的数据,类型为byte[]
String string = "Hello,I come form ICSS!";
byte[] databyte = new byte[100];
databyte = string.getBytes();
//创建数据报,封装了要发送的数据,数据长度,服务器地址,以及服务器端口为5000
DatagramPacket sendPacket = new DatagramPacket(databyte,
string.length(), InetAddress.getByName("127.0.0.1"), 5000);
//使用DatagramSocket对象将数据报sendPacket发送到服务器
sendSocket.send(sendPacket);
System.out.println("发送数据:" + string);
//创建DatagramSocket对象,用来接收数据,端口为5000
DatagramSocket receiveSocket = new DatagramSocket(5000);
byte buf[] = new byte[1000];
DatagramPacket receivePacket = new DatagramPacket(buf, buf.length);
System.out.println("startinig to receive packet");
while (true) {
//使用DatagramSocket接收数据报
receiveSocket.receive(receivePacket);
//解析数据报中的信息,获得主机名及端口、数据等
String name = receivePacket.getAddress().toString();
System.out.println("来自主机:" + name + "端口:"+ receivePacket.getPort());
String s = new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("接收数据: " + s);
}
private static ArrayList socketspool=new ArrayList();
//启动线程,在线程中处理请求
new Thread(new ThreadServer()).start();
//调用ServerSocket的accept方法,可以接受客户端的请求,并返回当前的Socket对象,加入到集合列表中
while(true){
socket = server.accept();
socketspool.add(socket);
如上所示,TCPServer02类,启动一个线程对每个客户端连接进行服务,同时在一个无限循环中接收客户端的请求,并把获得的socket对象存储在集合列表中;
while (true) {
//迭代列表中所有socket
for (int i=0;i
如上所示,ThreadServer类中,在一个无限循环中,迭代集合列表中的所有socket对象,读取客户端发过来的信息,并进行打印输出。
try {
//创建Socket对象
Socket socket = new Socket("127.0.0.1", 5700);
//获得键盘输入流
BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));
//获得与socket对象有关的输入输出流
DataOutputStream os = new DataOutputStream(socket.getOutputStream());
DataInputStream is = new DataInputStream(socket.getInputStream());
String readline;
// 向服务器写数据
while (true) {
readline = sin.readLine();
System.out.println(readline);
//向服务器写字符串
os.writeUTF(readline);
如上所示,TCPClient02类中,将键盘输入写到服务器端;
单一服务器对多客户端提供网络服务
ServerSocket server = null;
try {
server = new ServerSocket(6700);
System.out.println("服务器启动成功");
} catch (Exception e) {
System.out.println("服务器启动出错");
}
Socket socket = null;
try {
while (true) {
socket = server.accept();
MultiThreadServer st=new MultiThreadServer(socket);
Thread thread=new Thread(st);
clients.add(st);
thread.start();
}
如上所示,TCPServer03类中,在无限循环中接收请求,创建线程并启动线程;
is = new DataInputStream(socket.getInputStream());
os = new DataOutputStream(socket.getOutputStream());
while (true) {
line = is.readUTF();
System.out.println("Client "+socket.hashCode()+"说:" + line);
TCPServer03.sendToAll("Client "+socket.hashCode()+"说:"+line);
if (line.equals("exit")) {
break;
}
}
如上所示,MultiThreadServer类中,读取客户端发过来的信息,再群发到所有的客户端。
Runnable runnable = new Runnable() {
@Override
public void run() {
while (true) {
try {
String readline = is.readUTF();
System.out.println(readline);
if (readline.equals("exit")) {
break;
}
} catch (Exception ex) {
ex
}}}};
new Thread(runnable).start();
while (true) {
readline = sin.readLine();
os.writeUTF(readline);
}
HTTP,超文本传输协议,它是互联网上应用最为广泛的一种网络协议。HTTP是一种应用层协议,它是基于TCP协议之上的请求/响应式的协议。HTTP协议是Web浏览器和Web服务器之间通信的标准协议。HTTP指定客户端与服务器如何建立连接、客户端如何从服务器请求数据,服务器如何响应请求,以及最后如何关闭连接。HTTP连接使用TCP/IP来传输数据。
对于从客户端到服务器的每一个请求,都有4个步骤:
HTTP协议是基于TCP协议之上的请求/响应式协议,下面主要介绍HTTP报文的格式,HTTP报文主要有请求报文和响应报文两种。
首先看HTTP请求报文的格式:
HTTP请求报文由请求行、首部行和实体主体组成,由浏览器发送给服务器。上面这张图中SP表示空格,cr lf表示回车和换行。下图是谷歌浏览器内访问服务器查看的HTTP请求例子:
HTTP响应报文格式:
上面这张图是HTTP响应报文,它由状态行、首部行和实体主体组成。下图为HTTP响应报文例子:
在上面的HTTP请求报文例子中,我们可以看到请求方法是GET,这表示请求读取由URL所标志的信息,除了GET,还有其他几种常用的方法。
在HTTP响应报文的例子中,我们可以看到状态码是200,表示响应成功。下表是其他状态码,总共5大类,33种。
ServerSocket ss = new ServerSocket(9999);
Socket s = ss.accept ();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int num = in.read(buf);
String str = new String(buf,0,num);
System.out.println(s.getInetAddress().toString()+”:”+str);
s.close();
ss.close();
示例一客户端
示例一服务端
示例二客户端
示例二服务端
示例三(交互)客户端
示例三(交互)服务端
// 1 实例化客户端并连接服务端
Socket client = null;
try {
client = new Socket("127.0.0.1", 1245);
System.out.println("连接服务器成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("连接服务器失败!");
return;
}
// 2 客户端给服务端发送信息
// 利用DataOutputStream向服务端写入信息
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
DataInputStream dis = new DataInputStream(client.getInputStream());
int id = 0;
while (true) // 不断发送信息。间隔2秒钟。
{
dos.writeUTF("我是好学生" + id);
id++;
Thread.sleep(2000);
String str = dis.readUTF();
System.out.println("收到来自服务端的信息:" + str);
if (id == 10) // 只发10个包
break;
}
dos.close();
client.close();
一直等待一直下发消息服务端功能
ServerSocket server = null;
try {
// 1 启动监听
server = new ServerSocket(port);
System.out.println("在" + port + "端口启动监听成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("启动监听失败,端口可能被占用!");
return;
}
// 2 不断等待客户端连接
while (true) {
try {
Socket client = server.accept();
System.out.println("来自客户端" + client.getRemoteSocketAddress() + "的连接!");
// 3 读取来自客户端的消息
DataInputStream dis = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client.getOutputStream());
while (true) {
String str = dis.readUTF(); // 读取客户端的信息
System.out.println("收到客户端" + client.getRemoteSocketAddress() + "消息:" + str);
str = "响应-" + str;//
dos.writeUTF(str);// 服务器将信息发回给客户端
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("读取客户端信息失败,客户端可能已经断开!");
}
接受控制台输入的客户端-1
//连接成功后,先创建线程,不断从服务端读信息
class MyClientReceiveThread extends Thread
{
private Socket client; //本客户端的对象
public MyClientReceiveThread(Socket client) {
this.client = client;
}
@Override
public void run()
{
DataInputStream dis = null;
try {
dis = new DataInputStream(client.getInputStream());
while (true) {
String str = dis.readUTF();
System.out.println("收到来自服务端的响应:" + str);
}
} catch (Exception e)
{
}
finally
{
try {
dis.close();
} catch (Exception e2) {
}
}
}
}
接受控制台输入的客户端-2
System.out.println("这是接受控制台输入的客户端!!");
//1 实例化客户端并连接服务端
Socket client = null;
try {
client = new Socket("127.0.0.1",1245);
System.out.println("连接服务器成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("连接服务器失败!");
return;
}
//2 连接成功后,先创建线程,不断从服务端读信息
MyClientReceiveThread rt = new MyClientReceiveThread(client);
rt.start();//启动线程,不断从服务端读取信息
//3不断读取控制台内容
DataOutputStream dos = null;
try {
dos = new DataOutputStream(client.getOutputStream());
System.out.println("请输入:");
Scanner sc = new Scanner(System.in);
String readstr = sc.nextLine();//从控制读取内容
while (!readstr.toUpperCase().equals("QUIT")) //只要读取的内容不为QUIT,则???
{
dos.writeUTF(readstr); //往服务器写
sc.reset();
readstr = sc.nextLine();//写完后,继续从控制台读
}
} catch (Exception e)
支持多个客户端的Tcp服务端-1
//服务端使用的,专门与1个客户端对话的线程
class MyClientThread extends Thread
{
private Socket client; //对话的客户端端
public MyClientThread(Socket client)
{
this.client = client;
}
@Override
public void run()
{
System.out.println("来自客户端"+client.getRemoteSocketAddress()+"的连接!");
try {
//3 读取来自客户端的消息
DataInputStream dis = new DataInputStream(client.getInputStream());
DataOutputStream dos = new DataOutputStream(client
.getOutputStream());
while (true) {
String str = dis.readUTF(); //读取客户端的信息
System.out.println("收到客户端" + client.getRemoteSocketAddress()
+ "消息:" + str);
str = "响应-" + str;//
dos.writeUTF(str);//服务器将信息发回给客户端
}
支持多个客户端的Tcp服务端 -2
System.out.println("这是多线程的服务端!!");
int port = 1245;
ServerSocket server = null;
try {
// 1 启动监听
server = new ServerSocket(port);
System.out.println("在" + port + "端口启动监听成功!");
} catch (Exception e) {
e.printStackTrace();
System.out.println("启动监听失败,端口可能被占用!");
return;
}
// 2 不断等待客户端连接
while (true) {
try {
Socket client = server.accept();
// 创建线程对象,并启动
MyClientThread ct = new MyClientThread(client);
ct.start();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
实例一
下面是一个客户端和服务器端进行数据交互的简单例子,客户端输入正方形的边长,服务器端接收到后计算面积并返回给客户端,通过这个例子可以初步对Socket编程有个把握。
public class SocketServer {
public static void main(String[] args) throws IOException {
// 端口号
int port = 7000;
// 在端口上创建一个服务器套接字
ServerSocket serverSocket = new ServerSocket(port);
// 监听来自客户端的连接
Socket socket = serverSocket.accept();
DataInputStream dis = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));
do {
double length = dis.readDouble();
System.out.println("服务器端收到的边长数据为:" + length);
double result = length * length;
dos.writeDouble(result);
dos.flush();
} while (dis.readInt() != 0);
socket.close();
serverSocket.close();
}
}
public class SocketClient {
public static void main(String[] args) throws UnknownHostException, IOException {
int port = 7000;
String host = "localhost";
// 创建一个套接字并将其连接到指定端口号
Socket socket = new Socket(host, port);
DataInputStream dis = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));
Scanner sc = new Scanner(System.in);
boolean flag = false;
while (!flag) {
System.out.println("请输入正方形的边长:");
double length = sc.nextDouble();
dos.writeDouble(length);
dos.flush();
double area = dis.readDouble();
System.out.println("服务器返回的计算面积为:" + area);
while (true) {
System.out.println("继续计算?(Y/N)");
String str = sc.next();
if (str.equalsIgnoreCase("N")) {
dos.writeInt(0);
dos.flush();
flag = true;
break;
} else if (str.equalsIgnoreCase("Y")) {
dos.writeInt(1);
dos.flush();
break;
}
}
}
socket.close();
}
}
实例二
可以看到上面的服务器端程序和客户端程序是一对一的关系,为了能让一个服务器端程序能同时为多个客户提供服务,可以使用多线程机制,每个客户端的请求都由一个独立的线程进行处理。下面是改写后的服务器端程序。
public class SocketServerM {
public static void main(String[] args) throws IOException {
int port = 7000;
int clientNo = 1;
ServerSocket serverSocket = new ServerSocket(port);
// 创建线程池
ExecutorService exec = Executors.newCachedThreadPool();
try {
while (true) {
Socket socket = serverSocket.accept();
exec.execute(new SingleServer(socket, clientNo));
clientNo++;
}
} finally {
serverSocket.close();
}
}
}
class SingleServer implements Runnable {
private Socket socket;
private int clientNo;
public SingleServer(Socket socket, int clientNo) {
this.socket = socket;
this.clientNo = clientNo;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(
new BufferedInputStream(socket.getInputStream()));
DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(socket.getOutputStream()));
do {
double length = dis.readDouble();
System.out.println("从客户端" + clientNo + "接收到的边长数据为:" + length);
double result = length * length;
dos.writeDouble(result);
dos.flush();
} while (dis.readInt() != 0);
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println("与客户端" + clientNo + "通信结束");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
上面改进后的服务器端代码可以支持不断地并发响应网络中的客户请求。关键的地方在于多线程机制的运用,同时利用线程池可以改善服务器程序的性能。
实例三
//创建Socket,指定ip,port
Socket socket = new Socket("127.0.0.1", 8989);
//获得键盘输入
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();
readline = sin.readLine();
}
try {
ServerSocket server = null;
try {
//创建ServerSocket对象,指定端口是8989
server = new ServerSocket(8989);
System.out.println("服务器启动成功");
} catch (Exception e) {
System.out.println("服务器启动出错");
}
Socket socket = null;
try {
//调用ServerSocket的accept方法,可以接受客户端的请求
socket = server.accept();
} catch (Exception e) {
e.printStackTrace();
}