@1405010304geshuaishuai
2016-11-08T03:30:59.000000Z
字数 9150
阅读 763
TCP/IP
UDP
文件上传及下载
聊天小程序
<1>客户端接受用户输入的传输文件名。
<2>客户端请求服务器端传输该文件名所指文件。
<3>如果指定文件存在,在服务端就将其发送给客户端;反之,则断开连接。
通过实验掌握Socket编程
了解文件在客户端与服务器之间的传输方法
掌握文件在客户端到服务器之间的传输过程
客户端界面
说明:客户端有两个功能一个是向服务器发送文件另一个功能是从服务器下载文件,首先输入主机和端口号点击连接然后进行文件的发送,然后把要下载的文件名输入之后点击获取按钮即可下载,这里下载文件的路径设置在“D:\FTPService\”所以要想得到文件还请自己在D盘中创建FTPService文件夹。
服务器端没有界面。
文件在服务器端与客户端传输通过输入输出流来传输,首先通过DataInputStream来读取本地文件输入流,通过DataOutputStream将指令和文件发送到Socket输出流中,在内存中开辟一个缓冲区将本地文件读到缓冲区中然后再从缓冲区将文件写入到Socket输出流中,这个地方通过一个while循环直至缓冲区中没有数据就不再往Socket输出流中写数据。服务端通过接收put指令判断是客户端发送文件过来了于是准备接收。
服务器端首先在指定的文件夹下创建要接收的文件,然后通过写文件输入流将读取Socket的输入流至写文件的输出流。这样就把文件从客户端传到了服务端。
从服务端下载文件到客户端传输思路是一样的。客户端将要下载的文件名字传送给服务端,服务端接收到名字之后便去指定的文件夹下(D:\FTPService\)去找,如果有则发送发送完毕断开连接,如果没有直接断开连接。
(1) 新建一个项目。
(2) 在项目中创建一个JFrameDialog类的客户端,完成窗体的设计。
(3)在客户端写出client客户端连接函数、发送文件函数和接收文件函数。
(4)接收函数
/**
* 发送文件方法
*
* @param localFileName
* 本地文件的全路径名
* @param remoteFileName
* 远程文件的名称
*/
public void sendFile(String localFileName, String remoteFileName) {
DataOutputStream dos = null; // 写Socket的输出流
DataInputStream dis = null; // 读取本地文件的输入流
if (client == null)
return;
File file = new File(localFileName);
// 检查文件是否存在
省略代码
try {
// 初始化本地文件读取流
dis = new DataInputStream(new BufferedInputStream(new FileInputStream(file)));
// 将指令和文件发送到Socket的输出流中
dos = new DataOutputStream(client.getOutputStream());
// 将远程文件名发送出去
// System.out.println(remoteFileName);
dos.writeUTF("put " + remoteFileName);
// 清空缓存,将文件名发送出去
dos.flush();
// 开始发送文件
int bufferSize = 10240;
byte[] buf = new byte[bufferSize];
int num = 0;
while ((num = dis.read(buf)) != -1) {
dos.write(buf, 0, num);
}
dos.flush();
btnSend.setEnabled(false);
btnSelectfile.setEnabled(false);
btnConnect.setEnabled(true);
//System.out.println("文件发送成功!");
} catch (IOException e) {
//System.out.println("文件传输错误!");
closeSocket();
} finally {
try {
dis.close();
dos.close();
} catch (IOException e) {
System.out.println("输入输出错误!");
}
}
}
(5)发送文件函数
/**
* 接收文件方法
*
* @param remoteFileName
* 本地文件的全路径名
* @param localFileName
* 远程文件的名称
*/
public void receiveFile(String remoteFileName) {
DataOutputStream dos = null; // 写Scoket的输出流
DataInputStream dis = null; // 读Socket的输入流
DataOutputStream localdos = null; // 写本地文件的输出流
if (client == null)
return;
try {
// 将指令和文件名发送到Socket的输出流中
dos = new DataOutputStream(client.getOutputStream());
// 将远程文件名发送出去
dos.writeUTF("get " + remoteFileName);
dos.flush();
// 开始接收文件
dis = new DataInputStream(new BufferedInputStream(client.getInputStream()));
//System.out.println(dis.read());
int bufferSize = 10240;
byte[] buf = new byte[bufferSize];
int num = 0;
localdos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("D:\\" + remoteFileName)));
while ((num = dis.read(buf)) != -1) {
localdos.write(buf, 0, num);
}
localdos.flush();
//System.out.println("数据接收成功!");
} catch (FileNotFoundException e) {
//System.out.println("文件没有找到!");
closeSocket();
} catch (IOException e) {
//System.out.println("文件传输错误!");
} finally {
try {
dos.close();
localdos.close();
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
//if request is failed delete null file
File file = new File("D:\\" + remoteFileName);
if(file.exists()&&file.length()==0)
file.delete();
}
具体的传输过程已在关键技术中讲过在此不再赘述。
存在问题:
1、客户端从服务器获取文件,假如服务器无此文件,客户端也会创建一个0K的文件,解决办法,最后进行文件大小的判断,倘若下载的文件大小为0K则直接删除。
2、客户端在连接后进行文件发送,当文件发送完毕会断开连接,此时如果需要再次发送文件则需重新连接。而对于客户端接收文件时则不需要进行连接操作,因为你点击获取按钮之后会进行连接操作。
心得:
1、通过本次实验掌握了文件在客户端与服务端传输的过程;
2、遇到的问题是不知道如何将文件名输入发送给服务端然后服务端将文件返回,后来通过在互联网上查询找到了问题解决的办法。
3、遇到的问题,即使服务端没有客户端请求的文件,客户端也会在本地文件夹(D:\)创建一个空的文件,解决办法,最后加一条判断语句如果是空文件则删除。
4、掌握了客户端读取本地文件然后发送至服务端的方法。
虽然本次的软件基本功能能够达到,但是在软件的角度来说它还不是一个软件,由于本人水平有限,仍有很多不足,望多多指教。
1、进一步掌握GUI界面的编程
2、了解UDP传输方法及过程
回声客户端
使用说明:输入主机地址,输入端口号在下面的TextField中输入要发送的信息,点击发送结合在下面的输出框中看见来自服务器中的消息。
服务端没有界面。
UDP内部工作原理
IP的作用就是让离开主机B的UDP数据包准确传递到主机A。但把UDP包最终交给主机A的某一UDP套接字的过程则是由UDP完成的。UDP最重要的作用就是根据端口号将传到主机的数据包交付给最终的UDP套接字。
如果收发的数据量小但需要频繁连接时,UDP比TCP更高效。
UDP中的服务器端和客户端没有连接;
UDP服务器端/客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程。UDP只有创建套接字的过程和数据交换过程;
UDP服务器端和客户端均只需1个套接字。
服务器端,服务器采用Linux来作为服务器。
创建socket
serv_sock = socket(PF_INET, SOCK_DGRAM, 0);
为了创建UDP套接字,向socket函数第二个参数传递SOCK_DGRAM。
接收数据
str_len = recvfrom(serv_sock, message, BUF_SIZE, 0, (struct sockaddr*)&clnt_adr, &clnt_adr_sz);
得到分配的地址接收数据。不限制数据传输对象。
此函数成功返回接收的字节数,失败时返回-1。
serv_sock 用于接收数据的UDP套接字文件描述符;
message 保存接收数据的缓冲地址值;
BUF_SIZE 可接收的最大字节数,故无法超过参数buff所指的缓冲大小;
0 可选项参数,若没有则传入0;
clnt_adr 存有发送端地址信息的sockaddr结构体变量的地址值。
clnt_adr_sz 保存参数clnt_adr的结构体变量长度的变量地址值。
sendto(serv_sock, message, str_len, 0, (struct sockaddr*)&clnt_adr, clnt_adr_sz);
通过str_len函数调用同时获取数据传输端的地址。正是利用该地址将接收的数据逆向传输。
客户端用java来编写。
DatagramPacket sendPacket = new DatagramPacket(sendBuf, sendBuf.length, addr, port);
client.send(sendPacket);
通过此函数将数据包发送到指定IP的端口上。
DatagramPacket recvPacket = new DatagramPacket(recvBuf, recvBuf.length);
client.receive(recvPacket);
通过此函数来获取服务器传回来的消息。
通过本次实验,了解了UDP套接字的特点以及UDP的工作原理,了解了UDP内部工作原理,如果收发的数据量小但需要频繁连接时,UDP比TCP更高效。UDP服务器端/客户端不像TCP那样在连接状态下交换数据,因此与TCP不同,无需经过连接过程。UDP中只有创建套接字的过程和数据交换过程。只需1个UDP套接字就可以向任意主机传输数据。
服务器端:运行程序,服务器端等待客户端的连接,并显示客户端的连接信息,如图。
图 4.1 聊天服务器端
图4.1所示为有一个客户端连接到服务器,然后退出服务器的效果。
客户端:运行程序,用户登录服务器端后,可以从用户列表中选择单个用户进行聊天,也可以选择多个用户进行聊天,其中选择单个用户进行聊天的效果如图4.2.
图4.2 user1和user2聊天
服务器端:
使用Hashtable类来存储连接到服务器的用户名和套接字对象,并使用String类的startWith()方法判断客户端发送信息的类型,从而实现了向服务器端添加登录用户、发送退出信息、通过服务器转发客户端发送的信息等功能,最终完成了聊天服务器端程序的开发。
(1)Hashtable 类实现了一个哈希表,用于存储键值映射关系,任何非null对象都可以用作键和值的使用,构造方法定义如下:
public Hashtable()
参数说明
使用默认的初始容量11和加载因子0.75创建一个新的空哈希表。
(2)String类的startWith()方法,用于判断当前字符串是否以参数指定的字符串为前缀,该方法的定义如下:
public boolean startsWith(String prefix)
参数说明
prefix: 指定的前缀字符串。
返回值:如果该方法以指定的前缀开始,则返回true;否则返回false.
客户端:
通过线程对接收到的信息进行处理分为3种情况,第一种是接收到的是登录用户;第二种是接收到的是退出提示;第三种是接收到的是消息,实现这3种功能的关键代码如下:
String info = in.readLine().trim();// 读取信息
if (!info.startsWith("MSG:")) {// 接收到的不是消息
if (info.startsWith("退出:")) {// 接收到的是退出消息
model.removeElement(info.substring(3));// 从用户列表中移除用户
} else {// 接收到的是登录用户
boolean itemFlag = false;// 标记是否为列表框添加列表项,为true不添加,为false添加
for (int i = 0; i < model.getSize(); i++) {// 对用户列表进行遍历
if (info.equals((String) model.getElementAt(i))) {// 如果用户列表中存在该用户名
itemFlag = true;// 设置为true,表示不添加到用户列表
break;// 结束for循环
}
}
if (!itemFlag) {
model.addElement(info);// 将登录用户添加到用户列表
}
}
} else {// 如果获得的是消息,则在文本域中显示接收到的消息
DateFormat df = DateFormat.getDateInstance();// 获得DateFormat实例
String dateString = df.format(new Date()); // 格式化为日期
df = DateFormat.getTimeInstance(DateFormat.MEDIUM);// 获得DateFormat实例
String timeString = df.format(new Date()); // 格式化为时间
String sendUser = info.substring(4, info.indexOf(":发送给:"));// 获得发送信息的用户
String receiveInfo = info.substring(info.indexOf(":的信息是:") + 6);// 获得接收到的信息
ta_info.append(" " + sendUser + " " + dateString + " " + timeString + "\n " + receiveInfo
+ "\n");// 在文本域中显示信息
ta_info.setSelectionStart(ta_info.getText().length() - 1);// 设置选择起始位置
ta_info.setSelectionEnd(ta_info.getText().length());// 设置选择的结束位置
tf_send.requestFocus();// 使发送信息文本框获得焦点
}
(1)新建一个项目。
(2)完成界面设计。
(3)在客户端窗体类中,定义createCientSocket()方法,用于创建套接字对象、输出流对象以及启动线程对象对服务器转发的信息进行处理,该方法的代码如下:
public void createClientSocket() {
try {
Socket socket = new Socket("127.0.0.1", 1982);// 创建套接字对象
out = new ObjectOutputStream(socket.getOutputStream());// 创建输出流对象
new ClientThread(socket).start();// 创建并启动线程对象
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
(4)在客户端窗体类中,定义内部线程类ClientThread,用于对服务器端转发的信息进行处理,并显示在响应的空间中,该类的关键代码在关键技术中给出。
(5)在客户端窗体类中,为“登录”按钮添加实现用户登录功能的代码,“登录”按钮的事件代码如下:
if (loginFlag) {// 已登录标记为true
JOptionPane.showMessageDialog(null, "在同一窗口只能登录一次。");
return;
}
try {
InetAddress inetAddr = InetAddress.getLocalHost();
ip = inetAddr.getHostAddress();
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
String userName = tf_newUser.getText().trim()+ "("+ip+")";// 获得登录用户名
Vector v = new Vector();// 定义向量,用于存储登录用户
v.add("用户:" + userName);// 添加登录用户
try {
out.writeObject(v);// 将用户向量发送到服务器
out.flush();// 刷新输出缓冲区
} catch (IOException ex) {
ex.printStackTrace();
}
tf_newUser.setEnabled(false);// 禁用用户文本框
loginFlag = true;// 将已登录标记设置为true
}
(6)在客户端窗体类中,定义发送聊天信息的send()方法,该方法的代码如下:
private void send() {
if (!loginFlag) {
JOptionPane.showMessageDialog(null, "请先登录。");
return;// 如果用户没登录则返回
}
String sendUserName = tf_newUser.getText().trim();// 获得登录用户名
String info = tf_send.getText();// 获得输入的发送信息
if (info.equals("")) {
return;// 如果没输入信息则返回,即不发送
}
Vector<String> v = new Vector<String>();// 创建向量对象,用于存储发送的消息
Object[] receiveUserNames = user_list.getSelectedValues();// 获得选择的用户数组
if (receiveUserNames.length <= 0) {
return;// 如果没选择用户则返回
}
for (int i = 0; i < receiveUserNames.length; i++) {
String msg = sendUserName + ":发送给:" + (String) receiveUserNames[i]
+ ":的信息是: " + info;// 定义发送的信息
v.add(msg);// 将信息添加到向量
}
try {
out.writeObject(v);// 将向量写入输出流,完成信息的发送
out.flush();// 刷新输出缓冲区
} catch (IOException e) {
e.printStackTrace();
}
DateFormat df = DateFormat.getDateInstance();// 获得DateFormat实例
String dateString = df.format(new Date()); // 格式化为日期
df = DateFormat.getTimeInstance(DateFormat.MEDIUM);// 获得DateFormat实例
String timeString = df.format(new Date()); // 格式化为时间
String sendUser = tf_newUser.getText().trim();// 获得发送信息的用户
String receiveInfo = tf_send.getText().trim();// 显示发送的信息
ta_info.append(" "+sendUser + " " +dateString+" "+timeString+"\n "+receiveInfo+"\n");// 在文本域中显示信息
tf_send.setText(null);// 清空文本框
ta_info.setSelectionStart(ta_info.getText().length()-1);// 设置选择的起始位置
ta_info.setSelectionEnd(ta_info.getText().length());// 设置选择的结束位置
tf_send.requestFocus();// 使发送信息文本框获得焦点
}
对于文件的收发,调用外部的程序,此程序也就是实验中做过的文件收发程序,在此不再赘述。
通过本次实验我的收获:
(1)对tcp/ip通信进一步熟悉
(2)学习了如何给某一个特定的用户发送信息
(3)学会了如何群发
(4)在服务器端学会如何把用户名和它的IP绑定在一起
(5)学会了客户端如何接收服务端发送过来的消息
不足之处:
(1)收发文件功能不是很完善
(2)除了收发文字和文件其他功能还尚未开发
(3)···