一,Linux C++ Socket网络编程
1.什么是TCP/IP、UDP?
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。
UDP(User Data
Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。
下面的图表明了这些协议的关系。
2.Socket在哪里呢?
3.Socket是什么呢?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
门面模式,用自己的话说,就是系统对外界提供单一的接口,外部不需要了解内部的实现。
4.有很多的框架,为什么还在从Socket开始?
现在的跨平台网络编程框架很多,如Java的SSH,C/C++的Boost等。
现在的分布式框架很多,如Hadoop等。
我的任务是把一个C/C++程序做成分布式,要求的不配环境,基本属于纯计算的,结果很小。所以选择了Socket。
重要的是Socket是分布式、云计算、网络编程的基础,对Socket的学习有利于对其他框架的理解。
下图是Socket编程的基本流程:
第一步:建立一个socket
int socket(int af, int type, int protocol)
A. ‘int af‘代表地址族或者称为socket所代表的域,通常有两个选项:
1.
AF_UNIX - 只在单机上使用。
2.
AF_INET - 可以在单机或其他使用DARPA协议(UDP/TCP/IP)的异种机通信。
B.
‘int type‘代表你所使用的连接类型,通常也有两种情况:
1.
SOCK_STREAM - 用来建立面向连接的sockets,可以进行可靠无误的的数据传输
2. SOCK_DGRAM - 用来建立没有连接的sockets,不能保证数据传输的可靠性。
C.
‘int protocol‘通常设定为0。使系统选择默认的由协议族和连接类型所确定的协议。
D.
返回值是一个文件描述句柄,如果在此期间发生错误则返回-1并且设定了相应的errno。
int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket 创建出错!"); exit(1); }
第二步:绑定名字socket: bind()
int bind(int sockfd, struct sockaddr *name, int namelen)
A. sockfd是从socket()调用得到的文件描述句柄。
B. name是一个指向sockaddr类型结构的一个指针。
C. namelen给出了文件名的具体长度。
1. 如果地址族被设定为AF_UNIX,这个类型的定义如下所示:
struct sockaddr { u_short sa_family; char sa_data[14]; };
在这个结构种,name.sa_family应当被设定为AF_UNIX。
name.sa_data应当包含最长为14个字节的文件名,这个文件名用来分配给socket。
struct sockaddr name; name.sa_family = AF_UNIX; strcpy(name.sa_data, "/tmp/whatever"); int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { perror("socket 创建出错!"); exit(1); } if (bind(sockfd, &name, strlen(name.sa_data) + sizeof(name.sa_family)) == -1) { perror("bind"); exit(1); }
2. 在使用AF_INET地址族的时候,类型定义如下:
struct sockaddr_in { short int sin_family; unsigned short int sin_port; struct in_addr sin_addr; unsigned char sin_zero[8]; };
在这个结构种,name.sin_family应当被设定为AF_INET
struct sockaddr_in name; int sockfd; if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { exit(1); } name.sin_family = AF_INET; name.sin_port = htons(8087); name.sin_addr.s_addr = htonl(INADDR_ANY); bzero(&(name.sin_zero), 8); /* zero out the rest of the space */ if (bind(sockfd, (struct sockaddr *) &name, sizeof(struct sockaddr)) == -1) { exit(1); }
现在,如果没有问题的话,我们建立的socket就有一个名字了!相反,如果不成功,它会设定相应的错误代码,并使程序退出。
这里需要说明的是,如果你的计算机不想和别人的计算机连接,那么完全没有必要使用bind()。
第三步:远程连接: connect()
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
A. sockfd是我们建立的文件描述句柄
B. serv_addr是一个sockaddr结构,包含目的的地址和端口号
C. addrlen 被设定为sockaddr结构的大小。
if (connect(sockfd, (struct sockaddr *) &name, sizeof(name)) < 0) { exit(1); }
第四步:监听: listen()
int listen(int sockfd, int backlog)
B. 参数backlog是指一次可以监听多少个连接
if (listen(sockfd, 20) == -1) { exit(1); }
第五步:接受连接: accept()
当有人试图从我们打开的端口登陆进来时,我们应该响应他,这个时候就要用到accept()函数了。
int accept(int sockfd, void *addr, int *addrlen)
conn = accept(sockfd, (struct sockaddr*) &name, sizeof(name)); if (conn < 0) { exit(1); }
第六步:输入和输入的完成: send() and recv()
int send(int sockfd, const void *msg, int len, int flags)
int recv(int sockfd, void *buf, int len, unsigned int flags)
send():
sockfd - socket file descriptor
msg - message to send
len - size of message to send
flags - read ‘man send‘ for more info, set it to 0 for now
recv():
sockfd - socket file descriptor
buf - data to receive
len - size of buf
flags - same as flags in send()
注意:如果使用的连接类型是SOCK_DGRAM,那么应该使用sendto()和recvfrom()来实现数据传输。
char buffer[BUFFER_SIZE]; while (1) { memset(buffer, 0, sizeof(buffer)); int len = recv(conn, buffer, sizeof(buffer), 0); if (strcmp(buffer, "exit\n") == 0) break; fputs(buffer, stdout); send(conn, buffer, len, 0); }
结束: close() and shutdown()
当传输结束时,应当关闭连接。
一个服务端等待, 客户端上传文件到服务端,通过输入要上传的文件名,目前只做到仅对当前执行文件的目录下的文件,应该在服务端收到文件路径之后进行处理的。
服务端代码:
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <netdb.h> #include <arpa/inet.h>//for inet_ntoa #include <unistd.h>//for fork() #define SERVER_PORT 6666 #define LISTEN_QUEUE 20 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main() { //设置一个socket地址结构server_addr,代表服务器internet地址, 端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); //把一段内存区的内容全部设置为0 server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htons(INADDR_ANY); server_addr.sin_port = htons(SERVER_PORT); //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket int server_socket = socket(PF_INET, SOCK_STREAM, 0); if (server_socket < 0) { printf("Create Socket Failed!"); exit(1); } //把socket和socket地址结构联系起来 if (bind(server_socket, (struct sockaddr*) &server_addr, sizeof(server_addr))) { printf("Server Bind Port: %d Failed!\n", SERVER_PORT); exit(1); } //server_socket用于监听 if (listen(server_socket, LISTEN_QUEUE)) { printf("Server Listen Failed!"); exit(1); } while (1) { //定义客户端的socket地址结构client_addr char buffer[BUFFER_SIZE]; struct sockaddr_in client_addr; socklen_t length = sizeof(client_addr); int client_socket = accept(server_socket, (struct sockaddr*) &client_addr, &length); if (client_socket < 0) { printf("Server Accept Failed!\n"); break; } bzero(buffer, BUFFER_SIZE); // 获取客户端要传输的文件名 length = recv(client_socket, buffer, BUFFER_SIZE, 0); if (length < 0) { printf("Server Recieve Data Failed!\n"); break; } char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, FILE_NAME_MAX_SIZE + 1); strncpy(file_name, buffer, strlen(buffer) > FILE_NAME_MAX_SIZE ? FILE_NAME_MAX_SIZE : strlen(buffer)); // 新建文件 FILE * fp = fopen(file_name, "w"); if (NULL == fp) { printf("File: %s CAN NOT WRITE!\n", file_name); } else { bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while ((file_block_length = recv(client_socket, buffer, BUFFER_SIZE, 0)) > 0) { if (file_block_length < 0) { printf("Recieve Data From Client Failed!\n"); break; } int write_length = fwrite(buffer, sizeof(char), file_block_length, fp); if (write_length < file_block_length) { printf("File: %s Write Failed\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } fclose(fp); printf("File: %s Transfer Finished\n\n", file_name); } close(client_socket); } close(server_socket); return 0; }
客户端
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/wait.h> #include <netdb.h> #include <arpa/inet.h>//for inet_ntoa #include <unistd.h>//for fork() #define SERVER_PORT 6666 #define BUFFER_SIZE 1024 #define FILE_NAME_MAX_SIZE 512 int main() { //设置一个socket地址结构client_addr,代表客户机internet地址, 端口 struct sockaddr_in client_addr; bzero(&client_addr, sizeof(client_addr)); //把一段内存区的内容全部设置为0 client_addr.sin_family = AF_INET; //internet协议族 client_addr.sin_addr.s_addr = htons(INADDR_ANY); //INADDR_ANY表示自动获取本机地址 client_addr.sin_port = htons(0); //0表示让系统自动分配一个空闲端口 //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket int client_socket = socket(AF_INET, SOCK_STREAM, 0); if (client_socket < 0) { printf("Create Socket Failed!\n"); exit(1); } //把客户机的socket和客户机的socket地址结构联系起来 if (bind(client_socket, (struct sockaddr*) &client_addr, sizeof(client_addr))) { printf("Client Bind Port Failed!\n"); exit(1); } //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口 struct sockaddr_in server_addr; bzero(&server_addr, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVER_PORT); socklen_t server_addr_length = sizeof(server_addr); // 向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接 if (connect(client_socket, (struct sockaddr*) &server_addr, server_addr_length) < 0) { exit(1); } // 连接上服务器, 选择要上传的文件 char file_name[FILE_NAME_MAX_SIZE + 1]; bzero(file_name, FILE_NAME_MAX_SIZE + 1); printf("Please Input File Name Upload To Server: "); scanf("%s", file_name); char buffer[BUFFER_SIZE]; bzero(buffer, BUFFER_SIZE); strncpy(buffer, file_name, strlen(file_name) > BUFFER_SIZE ? BUFFER_SIZE : strlen(file_name)); FILE * fp = fopen(file_name, "r"); if (NULL == fp) { printf("File: %s NOT FOUND! \n", file_name); exit(1); } // 发送文件名 int nameLength = send(client_socket, buffer, BUFFER_SIZE, 0); if (nameLength < 0) { printf("File name Error! \n"); exit(0); } bzero(buffer, BUFFER_SIZE); int file_block_length = 0; while ((file_block_length = fread(buffer, sizeof(char), BUFFER_SIZE, fp)) > 0) { if (send(client_socket, buffer, file_block_length, 0) < 0) { printf("Send File:\t%s Failed\n", file_name); break; } bzero(buffer, BUFFER_SIZE); } printf("File:\t%s Transfer Finished\n", file_name); fclose(fp); close(client_socket); return 0; }
附上Windows Socket编程 http://www.cppblog.com/bujiwu/archive/2009/01/11/71707.aspx
二,JAVA ServerSocket
声明一个ServerSocket打开一个未占用的套接口,并开始ServerSocket.aceept 监听端口。
其他程序可以Socket连接IP与端口,new BufferReader(new InputStreamReader(socket.getInputStream()))接收消息。
import java.io.*; import java.net.*; import java.sql.*; import java.util.Vector; class ServerThread extends Thread { private Socket socket;// 定义套接口 private BufferedReader in;// 定义输入流 private PrintWriter out;// 定义输出流 Connection conn; int no; public ServerThread(Socket s) throws IOException {// 线程构造函数 socket = s;// 取得传递参数 in = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 创建输入流 out = new PrintWriter(new BufferedWriter(new OutputStreamWriter( socket.getOutputStream())), true);// 创建输出流 start();// 启动线程run } public void run() {// 重写run方法 System.out.println("监听Socket中..."); try { Class.forName("com.mysql.jdbc.Driver");// 连接数据库 conn = DriverManager.getConnection( "jdbc:mysql://localhost:3306/javaicq", "root", ""); while (true) { String OP = in.readLine(); // 停止 if (OP.equals("end")) break; // 登录 else if (OP.equals("login")) { try { PreparedStatement prepare = conn .prepareStatement("select nickname,password from icq where icqno=?");// 设定数据库查寻条件 int g = Integer.parseInt(in.readLine()); String passwd = in.readLine().trim(); prepare.setInt(1, g);// 设定参数 ResultSet rs = prepare.executeQuery();// 执行数据库查寻 if (!rs.next()) out.println("用户不存在"); else {// 以下比较输入的号码于密码是否相同 String pass = rs.getString("password").trim(); if (!passwd.equals(pass)) { out.println("密码错误"); } else { // 以及注册用户的ip 地址 PreparedStatement online = conn .prepareStatement("update icq set ip=? where icqno=?"); online.setString(1, socket.getInetAddress() .getHostAddress()); online.setInt(2, g); online.executeUpdate(); // set status online PreparedStatement status = conn .prepareStatement("update icq set status=1 where icqno=?"); status.setInt(1, g); status.executeUpdate(); out.println("YES"); } } rs.close(); } catch (Exception e) { e.printStackTrace(); out.println("Error"); } } // 新建 else if (OP.equals("new")) { try { // 准备接受用户的呢称,密码,email,个人资料,籍贯,头像等信息 String nickname = in.readLine().trim(); String password = in.readLine().trim(); String email = in.readLine().trim(); String info = in.readLine().trim(); String place = in.readLine().trim(); int picindex = Integer.parseInt(in.readLine()); PreparedStatement userInfo = conn .prepareStatement("insert into icq(nickname,password,email,info,place,pic,icqno) values(?,?,?,?,?,?,?)"); userInfo.setString(1, nickname); userInfo.setString(2, password); userInfo.setString(3, email); userInfo.setString(4, info); userInfo.setString(5, place); userInfo.setInt(6, picindex); userInfo.setInt(7, Integer.parseInt(Double.toString( Math.random()).substring(3, 8))); userInfo.executeUpdate();// 执行数据库添加 // 查询其注册的号码 PreparedStatement qNo = conn .prepareStatement("select icqno from icq where nickname=?"); qNo.setString(1, nickname); ResultSet rs = qNo.executeQuery(); while (rs.next()) { // 找到最近注册的号码 no = rs.getInt(1); } out.println("YES"); out.println(no); rs.close(); } catch (Exception e) { e.printStackTrace(); out.println("Error"); } } } } catch (IOException e) { e.printStackTrace(); } catch (SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { conn.close(); socket.close(); } catch (IOException e) { } catch (SQLException e) { } } } } public class Server {// 主服务器类 public static void main(String args[]) throws IOException { ServerSocket so = new ServerSocket(8081);// 在8081端口创建套接口 so.setSoTimeout(100000); System.out.println("服务器已启动 " + so); Socket testServer = new Socket(InetAddress.getLocalHost(), 8081); PrintWriter out = new PrintWriter(new BufferedWriter( new OutputStreamWriter(testServer.getOutputStream())), true); try { while (true) { Socket socket = so.accept();// 无限监听客户的请求 System.out.println("建立Socket连接:" + socket); try { // out.println("login"); // out.println(123); // out.println(123); new ServerThread(socket);// 创建新线程 } catch (IOException e) { socket.close(); } } } catch (SocketTimeoutException e) { System.out.println("超时断开连接"); } finally { so.close(); } } }