C/S模型就是server 与 client 的模型
TCP服务器模型流程图 TCP 客户端模型流程图:
函数使用:
(1)创建一个网络通信套接字描述符 int socket(int domain, int type, int protocol);
参数:domain : 协议系列,常用的是 AF_INET 表示IPV4
type : 常用的两个
SOCK_STREAM 流式套接字 TCP 常用
SOCK_DGRAM 数据报套接字 UDP 常用
protocol:一般为0
返回值:通信过程中使用的socket(一个文件描述符)
(2)把socket与 ip, 端口绑定一起 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数 :sockfd :通过socket 函数创建返回的通信文件描述符
addr:addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号协议类型等
addrlen: addrLen : sizeof (struct sockaddr_in) 是第二个参数addr结构所占的有效长度
struct sockaddr结构 和 struct sockaddr_in结构
struct sockaddr {
sa_family_t sa_family; //协议族
char sa_data[14]; // 14字节协议地址
}
struct sockaddr_in{
u_short sin_family;// 地址族, AF_INET,2 bytes
u_short sin_port; // 端口,2 bytes
struct in_addr sin_addr; // IPV4地址结构,4 bytes
char sin_zero[8]; // 8 bytes unused,作为填充
};
在bind时使用 struct sockaddr_in 结构进行初始化,然后通过 struct sockaddr 强制类型转换
sockaddr_in 与 sockaddr 结构的区别
(3) 监听绑定了ip和port的socket :int listen(int sockfd, int backlog);
参数:sockfd:监听连接的套接字
backlog 指定了完全成功连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。此参数现在基本已经不用。
返回值: 0 或 -1
完成listen()调用后,socket变成了监听
(4)阻塞等待连接 :int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数: sockfd : 监听套接字
addr : 对方地址
addrlen:地址长度
返回值:已建立好连接的套接字 或 -1
(5) 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数: sockfd :向哪个socket发送数据
buf :存放数据缓存首地址
len :发送的数据长度
flags:发送方式(通常为0)
返回值:成功:实际发送的字节数 失败:-1, 并设置errno
(6) 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数:socket:向哪个socket发送数据
buf : 发送缓冲区首地址
length : 发送的字节数
flags : 接收方式(通常为0)
返回值: 成功:实际接收的字节数 失败:-1, 并设置errno
(7) 客户端连接服务器的函数
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数:sockfd : socket返回的文件描述符
serv_addr : 服务器端的地址信息
addrlen : serv_addr的长度
返回值:0 或 -1
(8) 关闭通信socket int close(int fd);
编写TCP 的server和client 通信程序,服务器监听接收数据
server.c文件:
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/socket.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <string.h> #include <strings.h> #define MAX_BUF 1024 int main(int argc, const char *argv[]) { char buf[MAX_BUF] = {0};//接收数据缓存 int server_sock_fd; //服务器,socket描述符 int client_sock_fd; //链接的客户端,socket 描述符 ssize_t tybes; struct sockaddr_in server_addr;//存放服务器ip和端口 struct sockaddr_in client_addr;//存放客户端ip和端口 socklen_t len; len = sizeof(server_addr); bzero(&server_addr,len);//清零 server_sock_fd = socket(AF_INET, SOCK_STREAM,0);//创建socket,返回socket描述符 if(server_sock_fd < 0) { perror("socket fail "); exit(1); } //填充 地址和端口结构 server_addr.sin_family = AF_INET; //协议族 // server_addr.sin_port = htons(atoi(argv[2])); //服务器端口 //这两行是通过执行程序时传入参数ip和port // server_addr.sin_addr.s_addr = inet_addr(argv[1]);//IP地址 server_addr.sin_port = htons(atoi("8080")); //服务器端口 , htons 把主机字节序转为网络字节序,atoi 把字符数据转为整数 server_addr.sin_addr.s_addr = inet_addr("192.168.3.132");//IP地址 ,inet_addr把字符串ip转为网络字节序整数 if(bind(server_sock_fd, (struct sockaddr*)&server_addr, len) < 0) //绑定服务器 ip和port { perror("fail bind "); exit(1); } if( listen(server_sock_fd,10) < 0 ) //监听 服务器 { perror("fail listen "); exit(1); } while(1) { client_sock_fd = accept(server_sock_fd,(struct sockaddr*)&client_addr,&len);//阻塞等待客户端链接 if(client_sock_fd < 0) { perror("client socket fail "); exit(1); } printf("client ip %s port %u ",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port)); //打印链接的ip 和 port while(1) { memset(buf,0,1024); // tybes = recv(client_sock_fd,buf,10,0); tybes = read(client_sock_fd,buf,10); if(tybes <= 0) //断开链接或者出错 { puts("recv error !"); break; } printf("recv data : %s",buf);//IO缓冲 加 换行输出 if(strstr(buf,"quit") != NULL ) //接收到 “quit” 断开连接的客户端,等待下一个连接 { close(client_sock_fd); break; } } } close(server_sock_fd); return 0; }
client.c 文件;
#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <strings.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, const char *argv[]) { int client_sock_fd; char buf[20] = {0}; ssize_t send_tybes; struct sockaddr_in server_addr; socklen_t len; len = sizeof(server_addr); bzero(&server_addr,len); //清零 client_sock_fd = socket(AF_INET,SOCK_STREAM,0); //创建客户端 socket if(client_sock_fd < 0) { perror("client_sock_fd fail "); exit(1); } //填充服务器 地址 server_addr.sin_family = AF_INET; // server_addr.sin_port = htons(atoi(argv[2])); // server_addr.sin_addr.s_addr = (inet_addr(argv[1])); server_addr.sin_port = htons(atoi("8080")); server_addr.sin_addr.s_addr = (inet_addr("192.168.3.132")); if( connect(client_sock_fd, (struct sockaddr*)&server_addr, len) < 0) //连接服务器 { perror("connect fail "); exit(1); } while(1) { fgets(buf,10,stdin); //终端读取数据 send_tybes = send(client_sock_fd,buf,10,0); if(send_tybes <= 0) //接收数据失败或者断开连接 { printf("send fail !"); break; } if(strstr(buf,"quit") != NULL) //输入 “quit” 断开连接 { break; } } close(client_sock_fd); return 0; }
测试:此时,服务器只是接收数据,不能发送数据,因为接收和终端获取数据是两个阻塞函数,需要开启多进程或者多线程处理两个阻塞问题