才能的火花,常常在勤奋的磨石上迸发。 —— 威廉·李卜克内西
上一篇写了一些socket基本的本地通信,本篇文章说一下我们的socket网络通信。而网络通信分为TCP协议和UDP协议,这篇文章给出的是TCP协议。
TCP是传输层的协议,又为传输控制协议,在TCP通信中,我们需要必须的通信材料:端口和IP地址。
IP地址是网络中的唯一标识,通过IP地址即可找到我们的计算机,因为IP地址本质为一个整数,绑定我们网卡的MAC地址,即物理地址,物理地址是唯一的。IP又分为IPv4和IPv6,前者为32位整数,后者为128位整数,我们的IP地址一般用点分十进制表示,便于记忆,即8位16进制数表示,因此在编程的时候应该要转换。而为了更便于记忆,又推出了助记符,即域名/网址的概念,域名经过域名解析服务器完成IP的转换。
由于IP地址只能定位计算机,而不能定位在计算机中运行的程序,即进程,所以我们用端口对外管理进程,端口本质是一个非负的short型整数,(0~65535),因为short为两个字节,所以我们的端口号也是两个字节来表示,一般有些端口被固定的服务器所占用,所以我们选用端口号的时候尽量选择大的,大于4096位普通用户可以使用。
常用端口号:
服务器 | 端口号 |
---|---|
HTTP | 80 |
FTP | 21 |
TFTP | 69 |
SSH | 22 |
Oracle | 1521 |
1080 |
当然,端口号我们可以自己进行修改和定义,但是这样做无疑是作死。
而由于我们的计算机存储数据的方式不同,有的是大端,有的是小端,网络的存储格式为大端的,即高地址对应高数据位,小端则相反,因此我们统一将IP转换为网络大端字节序。
和PIC本地通信一样,网络通信也是非常的套路,但是其中很多细节的设置以及技巧用法耐人寻味,这里介绍的是多进程的编程,多进程编程耗费CPU资源较大,但是限于笔者能力,只能先结合网络的力量写一个多进程的socket编程
先介绍要用到的结构体以及相关函数:
#include <netinet/in.h>
struct sockaddr_in{
int sin_family; //用于指定协议族,和socket一致
short sin_port; //端口号
struct sin_addr s_addr; //存储IP地址的结构
}
这些用作参数时同样要转换为sockaddr类型。
socket(int domain, int type, int protocol);
bind(int sockfd, struct sockaddr* addr, socklen_t size);
connect(int sockfd, struct sockaddr* addr, socklen_t size);
listen(sockfd, len);
accept(int fd, struct sockaddr* addr, socklen_t* len);
socket、bind、connect函数在前面已经介绍过,这里不做重复
listen函数用于与多个客户端同时请求时,放入等待队列中,第二个参数为等待队列最大长度
accept函数是比较重要的函数,第一个参数为socket的返回值,addr为一个传入传出参数,传入addr的真实长度,传出接收到的客户端的通信地址的真实长度,当然,这两个值一般都相同。返回成功,返回值为一个新的socket描述符,以后即可对这个新的描述符进行读写操作,失败则返回-1。
接下来说一下编程的步骤吧:
服务器
1、socket
2、准备我们的通信地址结构体
3、服务器端为bind
4、监听listen
5、等待客户端连接,函数accept,返回新的描述符用于读写交互,accept相当于阻塞函数
6、读写函数
7、关闭socket
客户端与前面的一样,只是多了准备通信地址结构体。
下面给出本人结合其他信息的socket多进程程序:
服务器端:
/*********************************************************************************
* Copyright: (C) 2017 tangyanjun<519656780@qq.com>
* All rights reserved.
*
* Filename: myserver.c
* Description: This file
*
* Version: 1.0.0(06/06/2017)
* Author: tangyanjun <519656780@qq.com>
* ChangeLog: 1, Release initial version on "06/06/2017 06:57:03 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <signal.h>
int fd;
void fa(int signo) //用一个信号正常关闭文件描述符
{
printf("服务器即将关闭!\n");
sleep(2);
close(fd);
exit(0);
}
/*
void TIME()
{
time_t timep;
time(&timep);
printf("当前时间:%s", ctime(&timep));
}
*/
void TIME() //一个现实当前时间的函数
{
char buf[100] = {};
time_t cur_time = time(0);
struct tm* cur = localtime(&cur_time);
sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d", cur->tm_year+1900, cur->tm_mon+1, cur->tm_mday, cur->tm_hour, cur->tm_min, cur->tm_sec);
printf("当前时间:%s\n", buf);
}
int main(int argc, char **argv)
{
TIME();
printf("请按ctrl+C退出服务器\n");
signal(SIGINT, fa); //发送信号关闭文件描述符
fd = socket(AF_INET, SOCK_STREAM, 0); //socket
if (fd == -1) perror("socket"), exit(-1);
struct sockaddr_in addr; //定义一系列结构体
addr.sin_family = AF_INET;
addr.sin_port = htons(2222);
addr.sin_addr.s_addr = inet_addr("10.154.216.80");
int reuse = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); //避免地址冲突
int res = bind(fd, (struct sockaddr*)&addr, sizeof(addr)); //绑定
if (res == -1) perror("bind"), exit(-1);
printf("bind ok!\n");
listen(fd, 100); //监听
while(1){
struct sockaddr_in from;
socklen_t len = sizeof(from);
int newfd = accept(fd, (struct sockaddr*)&from, &len); //有客户连接,则返回新的描述符
if (newfd == -1) perror("accept"), exit(-1);
printf("%s连接了\n", inet_ntoa(from.sin_addr));
TIME();
pid_t pid = fork(); //有客户来时,创建一个子进程来处理
if(!pid)
{
char buf[100] = {};
while(1){
res = read(newfd, buf, sizeof(buf)); //对新的描述符进行读写操作
TIME();
printf("读到了%d个字节,内容是%s\n", res, buf);
if (res == -1) perror("read"), exit(-1);
else if(!res) break;
if (!strcmp(buf, "byebye")) //当客户输入相应字符时,可知客户端退出了
{
printf("%s退出了\n", inet_ntoa(from.sin_addr));
break;
}
write(newfd, buf, strlen(buf));
memset(buf, 0, strlen(buf)); //清空buf
}
close(newfd); //子进程关闭新的描述符
exit(0);
}
close(newfd); //父进程关闭新的描述符
}
// close(fd);
return 0;
}
客户端:
/*********************************************************************************
* Copyright: (C) 2017 tangyanjun<519656780@qq.com>
* All rights reserved.
*
* Filename: myclient.c
* Description: This file
*
* Version: 1.0.0(06/06/2017)
* Author: tangyanjun <519656780@qq.com>
* ChangeLog: 1, Release initial version on "06/06/2017 07:12:06 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <time.h>
#include <arpa/inet.h>
#include <time.h>
/* void TIME()
{
time_t timep;
time(&timep);
printf("当前时间:%s", ctime(&timep));
}
*/
void TIME() //时间函数
{
char buf[100] = {};
time_t cur_time = time(0);
struct tm* cur = localtime(&cur_time);
sprintf(buf, "%4d-%02d-%02d %02d:%02d:%02d", cur->tm_year+1900, cur->tm_mon+1, cur->tm_mday, cur->tm_hour, cur->tm_min, cur->tm_sec);
printf("当前时间:%s\n", buf);
}
int main(int argc, char **argv)
{
int fd = socket(AF_INET, SOCK_STREAM, 0); //socket
if (fd == -1) perror("socket"), exit(-1);
struct sockaddr_in addr; //定义结构体相关内容
addr.sin_family = AF_INET;
addr.sin_port = htons(2222);
addr.sin_addr.s_addr = inet_addr("10.154.216.80");
int res = connect(fd, (struct sockaddr*)&addr, sizeof(addr)); //连接服务器端
if (res == -1) perror("connect"), exit(-1);
printf("connect ok!\n");
char buf[100] = {};
char buf1[100] = {};
while(1){
TIME();
printf("请输入聊天内容:\n");
scanf("%s", buf);
write(fd, buf, strlen(buf));
if (!strcmp(buf, "byebye")) break; //输入相应字符退出
int res = read(fd, buf1, strlen(buf1));
if (res == -1)
{
perror("read");
break;
}
printf("read:%s\n", buf1);
memset(buf, 0, strlen(buf));
memset(buf1, 0, strlen(buf1));
}
close(fd);
return 0;
}
运行结果:
这就是一个简单的一对多的多进程通,后续文章将会写多线程通信,以及多路复用。