socket多进程TCP通信

才能的火花,常常在勤奋的磨石上迸发。 —— 威廉·李卜克内西

上一篇写了一些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位普通用户可以使用。

常用端口号:

服务器端口号
HTTP80
FTP21
TFTP69
SSH22
Oracle1521
QQ1080

当然,端口号我们可以自己进行修改和定义,但是这样做无疑是作死。

而由于我们的计算机存储数据的方式不同,有的是大端,有的是小端,网络的存储格式为大端的,即高地址对应高数据位,小端则相反,因此我们统一将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;
}

运行结果:

这里写图片描述

这就是一个简单的一对多的多进程通,后续文章将会写多线程通信,以及多路复用。

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值