最近在学习计算机网络时老师布置了一个socket编程的作业,基于老师给出的服务器以及端口,以及发送消息的信息格式和返回信息的格式,实现了以下三个功能
第一个listall(),实现获取服务器文件列表
第二个put(),实现文件上传
第三个get(),实现文件下载
刚开始的时候,我是用python写的,但是苦于python太久没有接触过了,十分生疏,只好改用java来写。在写的过程中困难颇多,先是上网查找资料,然后到图书馆找了好几本书看,有几本书对我帮助十分的大,《java经典实例》电力出版社
话不多说下面记录了一些我碰到的困难,第一步套接字原理已经十分简单了,
基于 TCP 的套接字编程的所有客户端和服务器端都是从调用socket 开始,它返回一个套接字描述符。客户端随后调用connect 函数,服务器端则调用 bind、listen 和accept 函数。套接字通常使用标准的close 函数关闭,但是也可以使用 shutdown 函数关闭套接字。下面针对套接字编程实现过程中所调用的函数进程分析。
下面记录一下客户端用到的几个函数
socket 函数
套接字是通信端点的抽象,实现端对端之间的通信。与应用程序要使用文件描述符访问文件一样,访问套接字需要套接字描述符。任何套接字编程都必须调用socket 函数获得套接字描述符,这样才能对套接字进行操作。以下是该函数的描述:
1. /* 套接字 */
2.
3. /*
4. * 函数功能:创建套接字描述符;
5. * 返回值:若成功则返回套接字非负描述符,若出错返回-1;
6. * 函数原型:
7. */
8. #include <sys/socket.h>
9.
10. int socket(int family, int type, int protocol);
11. /*
12. * 说明:
13. * socket类似与open对普通文件操作一样,都是返回描述符,后续的操作都是基于该描述符;
14. * family 表示套接字的通信域,不同的取值决定了socket的地址类型,其一般取值如下:
15. * (1)AF_INET IPv4因特网域
16. * (2)AF_INET6 IPv6因特网域
17. * (3)AF_UNIX Unix域
18. * (4)AF_ROUTE 路由套接字
19. * (5)AF_KEY 密钥套接字
20. * (6)AF_UNSPEC 未指定
21. *
22. * type确定socket的类型,常用类型如下:
23. * (1)SOCK_STREAM 有序、可靠、双向的面向连接字节流套接字
24. * (2)SOCK_DGRAM 长度固定的、无连接的不可靠数据报套接字
25. * (3)SOCK_RAW 原始套接字
26. * (4)SOCK_SEQPACKET 长度固定、有序、可靠的面向连接的有序分组套接字
27. *
28. * protocol指定协议,常用取值如下:
29. * (1)0 选择type类型对应的默认协议
30. * (2)IPPROTO_TCP TCP传输协议
31. * (3)IPPROTO_UDP UDP传输协议
32. * (4)IPPROTO_SCTP SCTP传输协议
33. * (5)IPPROTO_TIPC TIPC传输协议
34. *
35. */
connect 函数
在处理面向连接的网络服务时,例如 TCP ,交换数据之前必须在请求的进程套接字和提供服务的进程套接字之间建立连接。TCP 客户端可以调用函数connect 来建立与 TCP 服务器端的一个连接。
但是我一开始碰到一个问题就是一开始connected已经建立了,可是在后来不知道什么原因链接重置了,显示connection reset,导致出错,后来弄了一天才解决,然后是要对输入输出进行了解,要知道网络中消息的传输是怎样传输的,用什么方式传送的,用字节流呢还是用字符流呢。
但是用java来写又出现了一个问题,因为传输作业上要求的消息格式为无符号整形,但是在java里是没有无符号整形这一说法的,这里参考了一下
提供的处理方案
方案一:如果在Java中进行流(Stream)数据处理,可以用DataInputStream类对Stream中的数据以Unsigned读取
方案二:利用Java位运算符,完成Unsigned转换。
主要原理通俗来讲就是用更大的数来表示
import java.io.*;
import java.net.Socket;
import java.net.UnknownHostException;
public class client {
void Listall(){
Socket socket = null;
BufferedReader br = null;
PrintWriter pw = null;//定义变量
try {
//客户端socket指定服务器的地址和端口号
socket = new Socket("XX ",xxx);请自行修改
System.out.println("Socket=" + socket);
br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
socket.getOutputStream().write(1);
socket.getOutputStream().flush();
String str;
while((str = br.readLine()) != null){
System.out.println(str);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
System.out.println("连接关闭......");
br.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new client().Listall();
}
}
下面是我listall成功后的效果截图,但是这里存在一点小问题,乱码没能解决
第二个put()函数,实现上传文件
上传文件主要是要对文件操作和文件传输要足够了解,由于服务器是老师搭的,发送消息的格式必须严格按照要求,所以我主体部分写好了之后一直在调试消息格式,最后在成功上传
import java.net.Socket;
import java.net.UnknownHostException;
public class iotest {
public static int PORT = XXX;//请自行修改
public static FileInputStream fis;
public static DataOutputStream dos;
static Socket socket = null;
static BufferedReader br = null;
static PrintWriter pw = null;//定义变量
public static void put() throws Exception{
try {
File file = new File("E:\\XinAn1502zhangxiang.txt");
//如果文件存在则
if (file.exists()) {
fis = new FileInputStream(file);
dos = new DataOutputStream(socket.getOutputStream());
dos.write(2);
dos.write(file.getName().length());
dos.writeInt(fis.available());
dos.write(file.getName().getBytes());
byte[] d = new byte[fis.available()];
fis.read(d);
dos.write(d);
dos.flush();
// 开始传输文件
byte[] bytes = new byte[4096];//定义一个byte数组
int length = 0;
long progress = 0;
//判断fis流里是否还有内容
while ((length = fis.read(bytes, 0, bytes.length)) != -1) {
dos.write(bytes, 0, length);
dos.flush();
progress += length;//文件大小
}
System.out.println("文件传输成功");
br = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
String str;
while ((str = br.readLine()) != null) {
System.out.println(str);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null)
fis.close();
if (dos != null)
dos.close();
socket.close();
}
}
public static void main(String[] args) throws IOException {
try{ // 客户端socket指定服务器的地址和端口号
socket = new Socket("XXX ", PORT);//请自行修改
System.out.println("Socket=" + socket);
put();
}catch (Exception e){
e.printStackTrace();
}
}
}
测试时
测试效果,用listall()返回
最后玩上瘾了,多试了几次
所以说,套接字还是很好玩的!!!!有没有!!
第三个get()实现下载文件,
到了这一步,我已经对套接字十分了解了,写起来也很快,但是在中间遇到一个问题,程序在进行到开始传输文件那里是开始阻塞了,
最后我用了几个system.out.println,去调试发现问题出在了一个while循环上,一开始我利用读取是否为空来判断,但很明显那个函数永远不会返回空置,所以我用了-1来作为我的判断语句,最后没毛病,成功通过
import java.io.*;
import java.net.Socket;
/**
* Created by samsung on 2017/3/14.
*/
public class download {
public static int PORT = XXX;//请自行修改
public static FileInputStream fis;
public static FileOutputStream fos;
public static DataOutputStream dos;
static Socket socket = null;
static BufferedReader br = null;
static PrintWriter pw = null;//定义变量
public static void get() throws Exception{
try {
dos = new DataOutputStream(socket.getOutputStream());
String s="Beethoven-SymphonyNo5.mp3";
int len=s.length();
dos.write(0);
dos.write(len);
byte[] b=s.getBytes();
dos.write(b);
dos.flush();
// 开始传输文件
System.out.println("开始接收文件!" + "\n");
byte[] bytes = new byte[7340032];//定义一个byte数组
int count=0;//字节数
File f = new File("E:\\Beethoven-SymphonyNo5.mp3");
fos=new FileOutputStream(f);
System.out.println("开始接收文件!" + "\n");
while((count=socket.getInputStream().read(bytes)) != -1){
fos.write(bytes,0,count);
}
System.out.println("开始接收文件!" + "\n");
fos.flush();
fos.close();
System.out.println("接收完成");
System.out.println("文件传输成功");
//传输完成后服务器对服务器返回信息的接收
String fanhuixinxi;
while ((fanhuixinxi = br.readLine()) != null) {
System.out.println(fanhuixinxi);
}//接收返回信息并打印
} catch (Exception e) {
e.printStackTrace();
} finally {
if (dos != null)
dos.close();
socket.close();
}
}
public static void main(String[] args) throws IOException {
try{ // 客户端socket指定服务器的地址和端口号
socket = new Socket("XXX ", PORT);
System.out.println("Socket=" + socket);
get();
}catch (Exception e){
e.printStackTrace();
}
}
}
下载截图
之所以有三个接收文件是我在调试的原因
最后从服务器上下载了一首贝多芬,简直天籁有木有,曲子有6分56,但是有个问题是我并没有对返会的消息进行处理也能正常播放,这是比较奇怪的
最后整个socket编程的练习算是完成了
感想很多,不一一细说,以后还是要多多动手呀