网络编程复习笔记
UDP和TCP
TCP协议提供了一种可靠的数据传输服务,它是一种面向连接的数据传输协议。在数据传输之前,通信节点之间必须建立起连接。为确保正确地接收数据,TCP协议要求在目标电脑成功收到数据时发回一个确认(即ACK)。如果在某个时限内未收到相应的ACK,将重新传送数据包。如果网络拥塞,这种重新传送将导致发送的数据包重复。但是,接收电脑可使用数据包的序号来确定它是否为重复数据包,并在必要时丢弃它。UDP协议是一种面向无连接的数据传输服务,它不能保证数据包以正确的顺序被接收。该协议不能保证数据准确无误地到达目的地。UDP在许多方面非常有效。当某个程序的目标是尽快地传输尽可能多的信息时(其中任意给定数据的重要性相对较低),可使用UDP协议。QQ、ICQ等聊天软件使用UDP协议发送消息。
UDP编程:
1. DatagramSocket类
此类表示用来发送和接收数据报包的套接字。
主要的构造函数:
public DatagramSocket()
构造数据报套接字并将其绑定到本地主机上任何可用的端口。
public DatagramSocket(int port)
创建数据报套接字并将其绑定到本地主机上的指定端口。
public DatagramSocket(int port,InetAddress laddr)
创建数据报套接字,将其绑定到指定的本地地址。
当作为发送端时,可以不用参数,选择第一种构造方法,即无参数的构造方法。有多个IP地址时,应该使用第三个构造方法,指定发送端的IP。
常用的方法:
close() 关闭此数据报套接字。
send(DatagramPacket p) 从此套接字发送数据报包。
receive(DatagramPacket p) 从此套接字接收数据报包。
2. DatagramPacket类
此类表示数据报包。数据报包用来实现无连接包投递服务。每条报文仅根据该包中包含的信息从一台机器路由到另一台机器。从一台机器发送到另一台机器的多个包可能选择不同的路由,也可能按不同的顺序到达。不对包投递做出保证。
主要的构造函数:
public DatagramPacket(byte[] buf, int length)
构造 DatagramPacket,用来接收长度为 length 的数据包。
Public DatagramPacket(byte[] buf,int length,InetAddress address,int port)
构造数据报包,用来将长度为 length 的包发送到指定主机上的指定端口号。length 参数必须小于等于 buf.length。 参数: buf - 包数据。 length - 包长度。 address - 目的地址。 port - 目的端口号。
当DatagramPacket类作为发送端的时候,需要选择第二种构造方法,因为需要指定目的地的地址和端口号。而作为接收端的时候就不是那么的需要了。
常用的方法:
getAddress() 最好在接着调用getHostAddress() 返回 IP 地址字符串(以文本表现形式)。
getData() 返回数据缓冲区
getLength() 返回将要发送或接收到的数据的长度。
getPort() 返回某台远程主机的端口号,此数据报将要发往该主机或者是从该主机接收到的。
setAddress(InetAddress iaddr) 设置要将此数据报发往的那台机器的 IP 地址。
setData(byte[] buf) 为此包设置数据缓冲区。
setLength(int length) 为此包设置长度
setPort(int iport) 设置要将此数据报发往的远程主机上的端口号。
3. 最简单的UDP程序:
字符串与字节数组之间的双向转换。
String str = “heima”; byte [] buf = str. getBytes();
UDP接收程序必须先启动运行,才能接收UDP发送程序发送的数据。
用start命令来打开新命令行窗口的好处:新开启的一个命令行窗口,和原先的命令行具有相同的环境。
解决发送中文字符串的问题。
需要将含中文字符的字符串转换为字节数组了,再求其长度(发送的长度)。一个中文占2个字节,如果没有转换为字节数组,发送数据和包装数据的字节数组的长度不匹配。
一个简单的UDP聊天程序
package netUDPChat;
import java.awt.BorderLayout;
import java.awt.List;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class MainPanel extends JFrame {
JTextField jtfMsg = new JTextField(22);
JTextField jtfIP = new JTextField(22);
List list = new List();
DatagramSocket ds = null;
public MainPanel(){
setSize(500, 500);
setTitle("聊天");
this.setLocation(300, 100);
JPanel jp = new JPanel();
jp.setLayout(new BorderLayout());
jp.add(jtfIP,BorderLayout.WEST);
jp.add(jtfMsg,BorderLayout.EAST);
this.add(list,BorderLayout.CENTER);
this.add(jp,BorderLayout.SOUTH);
//将窗口设置成不可改变大小的。
this.setResizable(false);
try {
//注意这里的端口号必须和发送端的一致
ds = new DatagramSocket(8000);
} catch (SocketException e1) {
e1.printStackTrace();
}
jtfMsg.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
byte[] buf = jtfMsg.getText().getBytes();
InetAddress ia;
try {
ia = InetAddress.getByName(jtfIP.getText());
DatagramPacket dp = new DatagramPacket(buf,buf.length,ia,8000);
ds.send(dp);
} catch (Exception e1) {
e1.printStackTrace();
}
//发送数据后将发送信息的文本框置空。
jtfMsg.setText("");
}
});
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
ds.close();
System.exit(0);
}
});
new Thread(new Runnable(){
@Override
public void run() {
byte [] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf,buf.length);
while(true){
try {
ds.receive(dp);
String strInfo = new String(buf,0,dp.getLength()) + " from " +
dp.getAddress().getHostAddress()+":" + dp.getPort();
//让接收到得消息都显示在最上边。
list.add(strInfo, 0);
} catch (IOException e) {
if(!ds.isClosed()){
e.printStackTrace();
}
}
}
}
}).start();
setVisible(true);
}
public static void main(String[] args) {
new MainPanel();
}
}
UDP 服务器和客户端程序
package netSimpleUDP;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class MyReceiver {
public static void main(String[] args) throws Exception{
//作为接收端的时候 ,最好指定端口号
DatagramSocket ds = new DatagramSocket(8888);
byte []buf = new byte[1024];
//创建接收端得数据报包,指定接受的长度
DatagramPacket dp = new DatagramPacket(buf,buf.length);
//接受数据报包
ds.receive(dp);
System.out.println( new String(dp.getData(),0,dp.getLength())+
dp.getAddress().getHostAddress()+ ":" + dp.getPort());
//关闭数据报套接字
ds.close();
}
}
package netSimpleUDP;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class MySender {
public static void main(String[] args) throws IOException {
//创建发送端的数据报套接字
DatagramSocket ds = new DatagramSocket();
//需要发送的信息
String strInfo = "Hello,jack!好样的!";
/*
* 创建发送端的数据报包,包含包数据、包长度、目的地址、目的端口号
需要将含中文字符的字符串转换为字节数组了,再求其长度.
一个中文占2个字节,如果没有转换为字节数组,发送数据和包装数据的字节数组的长度不匹配。
*/
DatagramPacket dp = new DatagramPacket(strInfo.getBytes(),strInfo.getBytes().length,
InetAddress.getByName("127.0.0.1"),8888);
//发送数据报包
ds.send(dp);
//关闭数据报套接字
ds.close();
}
}
TCP编程:
TCP客户端程序与服务器端程序的交互过程:
(1) 服务器端创建一个ServerSocket,然后调用accept来等待客户端的连接
(2) 客户端创建一个Socket,并请求与服务器建立连接
(3) 服务器接受客户端的连接请求,并创建一个新的Socket与该客户建立专线连接。
(4) 建立了连接的两个Socket存在一个单独的线程(有服务器程序创建)上运行。
(5) 服务器开始等待新的连接请求。当新的连接请求开始时,重复(2)到步骤(5)的过程。
1. ServerSocket类
主要的构造方法:
public ServerSocket()
创建非绑定服务器套接字。
public ServerSocket(int port)
创建绑定到特定端口的服务器套接字。端口0 在所有空闲端口上创建套接字。
accept()方法 侦听并接受到此套接字的连接。
close()方法 关闭此套接字。
2. Socket类
主要的构造方法:
public Socket()
public Socket(InetAddress address,int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号。
public InputStream getInputStream()返回此套接字的输入流。
public OutputStream getOutputStream()返回此套接字的输出流。
* 会使用telnet命令
* 当我们编写了服务器端程序的时候,我们没有必要马上编写客户端程序
* 我们可以先用telnet命令测试下服务器端得程序是否正确。格式如下:
* telnet IP地址 端口号
*
* TCP服务器模型的编写要点
* 1.TCP服务器程序要想接收多个客户端的连接,需要循环调用ServerSocket的accept方法
* 2.服务器程序与每个客户端的会话过程不能互相影响,需要在独立的线程中运行
* 3.一个线程服务对象与一个客户端Socket对象相关联,共同来完成与一个客户端的会话。
* 如何检测和解决端口冲突问题
使用netstat命令查看当前正在被使用的TCP端口号。 netstat –na
TCP上对象的传递
package ObjectTCP;
import java.io.Serializable;
/*
* 用TCP传递的对象必须实现Serializable接口
*/
public class Student implements Serializable{
private int id;
private String name;
private int age;
private String department;
public Student(int id, String name,int age, String department) {
this.age = age;
this.department = department;
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
}
package ObjectTCP;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class ObjectServer {
/**
* 传递对象的服务器程序:
* 服务器端接收客户端传过来的对象。
*/
public static void main(String[] args) throws Exception{
//创建ServerSocket对象
ServerSocket ss = new ServerSocket(8002);
//等待连接
Socket s = ss.accept();
InputStream in = s.getInputStream();
//将字节输入流包装到对象输入流中
ObjectInputStream ois = new ObjectInputStream(in);
//读取对象
Student stu = (Student) ois.readObject();
//打印读取到得对象的信息
System.out.println("id = " + stu.getId() + ",name= " + stu.getName() +
",age= " + stu.getAge() + ",department= " + stu.getDepartment());
//关闭对象输入流
ois.close();
//关闭Socket套接字
s.close();
//关闭服务器
ss.close();
}
}
package ObjectTCP;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
public class ObjectClient {
/**
* TCP传递对象的客户端程序:
* 客户端程序将对象传递给服务器端
*/
public static void main(String[] args) throws Exception{
//建立连接
Socket s = new Socket("127.0.0.1",8002);
//获得字节输出流对象
OutputStream out = s.getOutputStream();
//创建Student对象
Student stu = new Student(01,"jack",21,"Math");
//用对象输出流包装字节输出流对象
ObjectOutputStream oos = new ObjectOutputStream(out);
//将Student对象写入到对象服务器端
oos.writeObject(stu);
//关闭对象输出流
oos.close();
//关闭Socket套接字
s.close();
}
}
URL(Uniform Resourse Locator) 统一资源定位符
URL的组成:协议名、主机名、端口号、资源名
如:http://locahost:8080/index.html
相对URL
Java.net包中提供了对URL进行编码和解码的类URLEncoder 和URLDecoder
package tcp;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class TCPServer {
/**
* 实现功能:
* 编写TCP服务器程序能够与多个客户端会话,客户端每次向服务器发送一行文本
* 服务器就将这行文本中的所有字符反向排列后回送给客户端,
* 当客户端发送"quit"时,服务器结束与客户端的连接。
*/
public static void main(String[] args) {
try {
ServerSocket ss = null;
/* 通过一个配置参数来指定TCP服务程序所使用的端口号
if(args.length <1){
ss = new ServerSocket(8001);
}else{
ss = new ServerSocket(Integer.parseInt(args[0]));
}*/
/*
* 将用户所指定的端口号保存到一个文件中,
* 当服务器程序下次启动时,直接从文件中读取端口号,
* 当要修改端口号时,直接从文件里修改就可以了。
*/
File portFile = new File("port.txt");
BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(portFile)));
int port = Integer.parseInt(br.readLine());
ss = new ServerSocket(port);
//服务器一直启动
while(true){
//创建Socket 及启动与之相应的线程
Socket s = ss.accept();
new Thread(new ServerThread(s)).start();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
package tcp;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/*
* 线程类,一个客户端对应着一个线程
*/
public class ServerThread implements Runnable{
private Socket s = null;
public ServerThread(Socket s){
this.s = s;
}
@Override
public void run() {
try{
//获得输入字节流
InputStream in = s.getInputStream();
//获得输出字节流
OutputStream out = s.getOutputStream();
//包装类,实现字节转换为字符操作
BufferedReader br = new BufferedReader(new InputStreamReader(in));
/*实现行输出,换行与与操作系统有关,windows为"\r\n",linux为"\n"
* 而PrintStream的println的换行符与操作系统无关,都为"\n"
* PrintWriter 注意他的缓存问题:即写入的内容不立即输出
* 解决的方法:构造方法设置为自动刷新缓存,和println配合使用
*/
PrintWriter pw = new PrintWriter(out,true);
/*
* 此处注意一个小问题就是
* 当我们用telnet启动一个客户端时,输入abd[backspace]c 命令行显示abc
* 当时转换后为dba 这是因为 转换后为c[backspace]dba 命令行中显示dba
*/
while(true){
//读取客户端输入的一行数据
String strLine = br.readLine();
//当客户端输入quit时客户端与服务器端失去连接
if(strLine.equalsIgnoreCase("quit")){
break;
}
String strEcho = new StringBuffer(strLine).reverse().toString();
//将发转的信息写入到输出流中
pw.println(strLine + "--->" + strEcho);
}
//各种关闭
br.close();
pw.close();
s.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
package tcp;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
public class TCPClient {
/**
* 客户端:
*/
public static void main(String[] args) throws Exception{
//通过配置参数来指定连接服务程序的IP地址和Port
if (args.length < 2){
System.out.println("User:Please use : java TCPClient TCPServerIP TCPServerPort");
return;
}
Socket s = new Socket(args[0],Integer.parseInt(args[1]));
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
//获得从输入流中读取一行的包装类
BufferedReader brNet = new BufferedReader(new InputStreamReader(in));
//获得从键盘中读取一行的包装类
BufferedReader brKeyBoard = new BufferedReader(new InputStreamReader(System.in));
//往输出流中写入一行
PrintWriter pw = new PrintWriter(out,true);
while(true){
//先从键盘读取一行
String str = brKeyBoard.readLine();
//将读取的一行字符写入到输出流中
pw.println(str);
//如果从键盘输入的字符为quit,那么结束客户端程序
if(str.equalsIgnoreCase("quit")){
break;
}
//读取输入流中的数据,将从服务器端返回的信息打印
System.out.println(brNet.readLine());
}
//各种关闭
brNet.close();
brKeyBoard.close();
pw.close();
s.close();
}
}
---------------------- android培训、 java培训、期待与您交流! ---------------------- 详细请查看: http://edu.csdn.net/heima