linux网络编程--socket服务器和客户端TCP编程及多进程编程
这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入欢迎使用Mar
·
文章目录
1.网络编程中客户端与服务器通信基本流程
2. 服务器和客户端编程实现<迭代服务器>
2.1. 迭代服务器编程实现
2.1.1. 命令行参数解析
- 服务器参数只有端口号,增加一个帮助参数<-h>,对该命令的用法进行说明
- 代码编写如下
void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-p(--port): sepcify server port.\n");
printf("-h(--Help): print this help information.\n");
return ;
}
{
int port = 0;
int ch;
struct option opts[] = {
{"port", required_argument, NULL, 'p'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while( (ch=getopt_long(argc, argv, "p:h", opts, NULL)) != -1 )
{
switch(ch)
{
case 'p':
port=atoi(optarg);//字符串 ----> 整形
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if(!port) //未正常使用命令
{
print_usage(argv[0]);
return 0;
}
}
2.1.2. 创建服务器 socket
{
socket_fd = socket(AF_INET, SOCK_STREAM, 0);//IPV4 TCP
if(socket_fd < 0)
{
printf("Create socket failure : %s\n", strerror(errno));
return -1;
}
printf("Create socket [%d] successful!\n", socket_fd);
setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));//防止程序端口重用报错
}
2.1.3. bind 绑定端口和ip 并且 开启listen
{
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port); // 主机字节序 ----> 网络字节序
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有的 IP 主机-----> 网络
rv = bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(rv < 0)
{
printf("Socket[%d] bind on port [%d] failure : %s\n",socket_fd,port,strerror(errno));
return -2;
}
printf("Socket[%d] bind on port [%d] successful!\n", socket_fd, port);
//listen
listen(socket_fd, 13);
}
2.1.4. 开启accept
- accept是一个阻塞函数,当 没有客户端连接服务器的时候,该程序会一直阻塞不返回,直到有一个客户端连接过来为止。当客户端调用connect函数就会触发服务器的accept函数返回,此时TCP连接就建立好了。
- 代码实现
//accept
clifd = accept(socket_fd, (struct sockaddr *)&cliaddr, &len);
if(clifd < 0)
{
printf("Accept new client failure:%s\n", strerror(errno));
continue;
}
printf("Accept new client [%s:%d] successfully!\n"
,inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));//客户端的IP和端口
2.1.5. 通过文件IO系统调用对客户端进行读写
- 读客户端编程实现,使用read系统调用
{
memset(buf, 0, sizeof(buf));
rv = read(clifd, buf, sizeof(buf));
if(rv < 0)
{
printf("Read from client by clifd[%d] failure: %s\n", clifd , strerror(errno));
close(clifd);
continue;
}
else if(rv == 0)
{
printf("Clifd[%d] get disconnected\n", clifd);
close(clifd);
continue;
}
else if(rv > 0)
{
printf("Read [%d] byte data from client clifd[%d] : %s\n", rv, clifd, buf);
}
}
- 对客户端进行write写消息,注意写完后不想继续通讯,需要关闭客户端描述符。
{
rv = write(clifd, MSG_STR, strlen(MSG_STR));
if(rv < 0)
{
printf("write to client by clifd[%d] failure: %s\n", clifd , strerror(errno));
close(clifd);
continue;
}
printf("Close client fd[%d]\n", clifd);
close(clifd);
}
- 服务器关闭前也需要把监听前创建的socket描述符关闭。
2.2. 客户端编程实现
2.2.1 客户端命令行参数解析(带域名解析功能)
- 客户端参数有 服务器ip地址和服务器端口,增加帮助命令<-h>,增加域名命令<-d>,该代码仅实现域名的解析并打印其ip地址,由于没有公网IP无法进行测试,测试baidu.com可以解析出其IP地址
{
int ch;
struct option opts[] = {
{"ipaddr", required_argument, NULL, 'i'},
{"port", required_argument, NULL, 'p'},
{"domain_name", required_argument, NULL, 'd'},
{"help", no_argument, NULL, 'h'},
{NULL, 0, NULL, 0}
};
while( (ch=getopt_long(argc, argv, "i:p:d:h", opts, NULL)) != -1 )
{
switch(ch)
{
case 'i':
servip=optarg;
break;
case 'p':
port=atoi(optarg);
break;
case 'd':
ser_domain_name=optarg;
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if( !servip || !port || !ser_domain_name)
{
print_usage(argv[0]);
return 0;
}
//打印域名和解析后的IP地址
p = gethostbyname(ser_domain_name);
printf("domain_name : %s servip : %s\n", ser_domain_name, inet_ntoa(*((struct in_addr *)p->h_addr)));
}
2.2.2. 创建客户端socket
- 使用IPV4和TCP通讯
{
socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if(socket_fd < 0)
{
printf("Create socket failure :%s\n", strerror(errno));
return -1;
}
printf("Create socket [%d] successful!\n", socket_fd);
}
2.2.3. 与服务器进行连接connect
- 把参数通过规定进行传入,注意字节序和字符串转换
{
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(port);// 主----> 网
inet_aton(servip, &servaddr.sin_addr); //字符串 -----> 网络字节序
rv = connect(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if(rv < 0)
{
printf("Connect to server [%s:%d] failure : %s\n", servip, port, strerror(errno));
return -2;
}
printf("Connect to server [%s:%d] successful!\n", servip, port);
}
2.2.4. 通过文件IO系统调用对服务器进行读写
- 和服务器一样,使用read和write进行消息的读写
- 注意客户端与服务器结束通讯后,不想继续通讯便可关闭socket描述符
{
rv = write(socket_fd, MSG_STR, strlen(MSG_STR));
if(rv < 0)
{
printf("Write to server by socket_fd [%d] failure : %s\n", socket_fd, strerror(errno));
break;
}
//read
memset(buf, 0, sizeof(buf));
rv = read(socket_fd, buf, sizeof(buf));
if(rv < 0)
{
printf("Read from server by sockfd[%d] failure: %s\n", socket_fd , strerror(errno));
break;
}
else if(0 == rv)
{
printf("Socketfd[%d] get disconnected\n", socket_fd);
break;
}
else if(rv > 0)
{
printf("Read [%d] byte from server socket_fd [%d] : %s\n", rv, socket_fd, buf);
}
close(socket_fd);
}
3. 服务器+多进程编程
3.1. 简单介绍多进程编程
- 迭代服务器一次只能与一个客户端进行通讯,而实际中会有大量和客户端与服务器进行访问通讯,此时迭代服务器便不再适用。
- 使用多进程编程实现并发服务器,结构框图如下
3.2 编程实现
- 多进程编程实现,在原有的服务器代码上进行修改,在接收到客户端连接请求后,开启子进程与客户端进行通讯
{
pid = fork();
if(pid < 0)
{
printf("fork() create child process failure : %s\n", strerror(errno));
close(cli_fd);
}
else if(pid > 0) // 父进程 的功能函数
{
close(cli_fd); //父进程 不需要 客户端描述符
continue;
}
else if(0 == pid)
{
close(socket_fd); //子进程不需要 该fd,该fd用于监听,监听到了后,使用新的fd
....//服务器与客户端通讯部分
}
}
更多推荐
已为社区贡献1条内容
所有评论(0)