一、课程设计目标与任务
目的:熟悉linux下socket、网络编程的基本方法,掌握实现客户/服务器程序的编写方法;
任务:编写一个简单的程序,该程序可实现基于TCP协议的简单的客户/服务器方式。
二、涉及的主要理论及算法思想
TCP协议,socket、网络编程
三、具体设计及开发思路
编写两个程序,一个客户端,一个服务器,使两个程序建立socket连接,以相互传送数据。
四、代码
Client:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define port 8888 int main() { //一、创建套接字socket int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket failed"); exit(-1); } //二、准备通信地址 struct sockaddr_in addr; addr.sin_family = AF_INET; //指定协议族,和socket()的参数保持一致 addr.sin_port = htons(port); //设置网络通信使用的端口号 inet_aton("192.168.1.129", &addr.sin_addr); //存储网络通信用ip地址 //三、连接服务器 int con = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)); if (con == -1) { perror("connect failed"); exit(-1); } printf("连接成功!\n"); //四、与服务器交换数据 char buf[100] = {0}; char *str = "hello server."; send(sockfd, str, strlen(str), 0); recv(sockfd, buf, sizeof(buf), 0); printf("收到来自服务器的信息:%s\n", buf); //五、关闭连接 close(sockfd); return 0; }
Server:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define port 8888 int main() { //一、创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd == -1) { perror("socket failed"); exit(-1); } //二、准备通信地址 struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); inet_aton("192.168.1.129", &addr.sin_addr); //三、绑定socket和通信地址 int bin = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)); if (bin == -1) { perror("bind failed"); exit(-1); } //四、监听端口 bin = listen(sockfd, 100); if (bin == -1) { perror("listen failed"); exit(-1); } printf("开始监听%d端口...\n", port); //五、处理连接请求 struct sockaddr_in fromaddr; socklen_t len = sizeof(fromaddr); int clientfd = accept(sockfd, (struct sockaddr *)&fromaddr, &len); if (clientfd == -1) { perror("accept failed"); exit(-1); } printf("%s连接\n", inet_ntoa(fromaddr.sin_addr)); //六、处理客户端数据 char buf[100] = {0}; recv(clientfd, buf, sizeof(buf), 0); printf("客户端:%s\n", buf); char *str = "hello client."; send(clientfd, str, strlen(str), 0); //七、关闭连接 close(clientfd); close(sockfd); return 0; }
五、程序运行效果展示截图
六、技术小结
1.基于TCP的网络编程:
基于连接, 在交互过程中, 服务器和客户端要保持连接, 不能断开。重发一切出错数据、数据验证, 保证数据的正确性、完整性和顺序性,
缺点是消耗的资源比较大。
2.编程步骤:
服务器:
① 创建socket(套接字) socket()
② 准备通信地址
③ 将创建的socket和通信地址绑定 bind()
④ 监听端口 listen()
⑤ 等待客户端连接 accpet()
⑥ 通信双方收发数据 read()/write()
send()/recv()
⑦ 关闭socket
客户端:
① 创建socket(套接字) socket()
② 准备通信地址
③ 连接服务器 connect()
④ 收发数据 read()/write()
send()/recv()
⑤ 关闭socket
3. API详解
(1) socket()函数
int socket(domain, type, protocol)
domain:
AF_UNIX/AF_LOCAL/AF_FILE: 本地通信
AF_INET: 网络通信 ipv4
AF_INET6: 网络通信 ipv6
注:如果AF换成PF效果一样
type, 选择通信类型, 主要包括:
SOCK_STREAM: TCP
SOCK_DGRAM : UDP
protocol, 本来应该指定通信协议, 但现在基本废弃, 因为协议已经在前面两个参数指定完成,给0即可
(2) bind()函数
int bind(int sockfd, struct sockaddr *addr, size)
sockfd: 要绑定的套接字描述符
size: 第二个参数占据的内存空间大小
addr: 涉及三个数据结构struct sockaddr, sockaddr_un, sockaddr_in
sockaddr, 主要用于函数参数, 不负责存储数据
sockaddr_un, 当着本地通信时, 用于本地通信使用的地址 (sys/un.h)
sockaddr_in, 当着网络通信时, 负责存储网络通信的地址数据
struct sockaddr_in
{
sin_family; //用于指定协议族, 和socket()的参数保持一致
sin_port; //网络通信使用的端口号
sin_addr; //存储网络通信的ip地址
}
(3) listen()函数
int listen(int sockfd, int backlog)
sockfd: 将sockfd参数所标识的套接字为被动模式, 使之可以接受连接请求
backlog: 表示未决连接请求队列的最大长度, 即允许最多有多少个未决连接请求存在。若服务器的未决连接请求已达到该值, 则客户端通过 connect()连接服务器的操作将返回-1,且error为ECONNREFUSED
(4) accpet()函数
int accpet(sockfd, struct sockaddr* addr, socklen_t *addrlen)
从sockfd参数所标识套接字对应未决连接请求队列中取出的一个连接请求, 同时创建一个新的套接字,用于该连接通信, 返回套接字的描述符
addr和addrlen 用于输出连接请求发起者的地址信息
返回值: 为新创建用于和客户端通信的套接字描述符 失败-1, error
(5) recv()函数
int recv(int sockfd, buf, len, flags)
flags, 通常取0: 阻塞收取数据
O_NONBLOCK: 不阻塞, 如果未收到数据, 返回错误信息
返回值:
>0, 实际接受数据字节数
-1 , 出错, error
0 , 通信的另一端关闭
(6)send()函数
int send(int sockfd, buf, len, flags)
flags: 通常取0, 阻塞发送
O_NONBLOCK: 不阻塞, 如果未收到数据, 返回错误信息
(7) connect()函数
int connect(int sockfd, addr, addr_len)
参数参考bind()