TCP的socket通讯

之前写过UDP的socket通讯,下面将介绍TCP的socket通讯,同时提供了TCP客户端与并发服务器端的几个案例。

目录

简介

 socket开发API介绍

TCP并发服务器


简介

与UDP的区别

 

                 TCP

                        UDP

是否面向连接

                    ✓

                        X

是否可靠

                    ✓

                        X

支持广播、多播

                    X

                        ✓

效率

                    低

                        高

客户端与服务器TCP通讯流程

                                                     

 socket开发API介绍

1、创建tcp套接字socket()

int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if ( sockfd < 0 )
{
    perror("socket");
    exit(-1);
}

开发TCP客户端需要满足以下几点:

        1、知道服务器的ip、port;

        2、需主动连接服务器;

        3、使用函数:socket()、connect()、send()、recv()、close()

Note:tcp的一些函数没有目的指向,比如是 send() 而不是 sendto(),这是因为 tcp 客户端与服务器通讯之前是先建立连接后通讯,使用该函数时,已经知晓目的地址。


2、connect连接函数

//主动与服务器建立连接
#include <sys/socket.h>
int connect(
    int sockfd;
    const struct sockaddr *addr,
    socklen_t len
);
参数介绍
    sockfd	:	socket
    addr	:	连接的服务器地址结构
    len		:	地址结构体长度
Return
    成功返回0
Note
    connect建立连接后不会产生新的套接字
    成功建立连接后才可以传输数据

3、send数据发送函数

//发送数据
#include <sys/socket.h>
ssize_t send(
    int sockfd,
    const void * buf,
    size_t nbtyes,
    int flags
);
参数
    sockfd	:socket
    buf		:发送的数据
    nbytes	:发送缓存数据大小
    flags	:套接字标志(通常为0)
Return	
    成功发送的数据字节数

4、recv函数

//接收网络数据,阻塞式
#include <sys/socket.h>
ssize_t recv(
    int sockfd,
    void *buf,
    size_t nbytes,
    int flags
);
参数
    sockfd	:socket
    buf		:接收网络数据的缓冲区
    nbytes	:接收缓冲区的大小(以字节为单位)
    flags	:套接字标志(通常为0)
Return
    成功接收的字节数

客户端发送与接受数据

以下代码将发送一次数据和接受数据,服务器可使用 NetAssist(windows)、linux下可使用自己开发的

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
int main(void)
{
    //create tcp socket
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if (sockfd < 0)
    {
        printf("CREATE SOCKET ERROR\n");
        return 0;
    }
    //bind是可选的, 固定ip和port
    struct sockaddr_in client;
    bzero(&client,sizeof(client));
    client.sin_family = AF_INET;
    client.sin_port = htons(8000);
    client.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd,,(struct sockaddr *)&client, sizeof(client));
    //connect 连接服务器
    struct sockaddr_in ser_addr;
    bzero(&ser_addr,sizeof(ser_addr));
    ser_addr.sin_port = htons(8080);
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = inet_addr("192.168.31.96");
    connect(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));
    //给服务器发送数据
    char buf[128] = "";
    printf("please enter the msg to send\n");
    fgets(buf,sizeof(buf),stdin);
    buf[strlen(buf) - 1] = 0;//消除回车的影响
    send(sockfd, buf, strlen(buf), 0);
    //接收数据
    char msg[128] = "";
    
    recv(sockfd, msg, sizeof(msg), 0);
    printf("received msg is %s\n",msg);
    close(sockfd);
    return 0;
}

开发服务器设计需要满足以下两点:

        1、确定的地址和端口

        2、监听(listen)与接收(accept)

设计流程:

        1、socket()(主动)

        2、bind() 固定ip和固定端口

        3、listen()(主动变被动、创建连接队列)

        4、accept() (从连接队列中提取成功建立的连接,接下来可以正式通讯)

        根据上图TCP通讯流程图可以看出,服务器绑定固定端口,监听客户端的请求。客户端与服务器通讯前,要先与服务器端成功建立连接,需要经过三次握手。成功建立的连接会放入“完成链接”,服务器将使用accept()函数从该队列中取出客户端请求并处理。


1、listen()函数

//将套接字由主动改为被动
//操作系统为该套接字设置一个队列,用来记录所有链接到该套接字的连接
int listen(
    int sockfd, 
    int backlog
  );
参数
    sockfd		:socket监听套接字
    backlog	:连接队列的长度
Return	
    成功返回 0

2、accept()函数

//从已建立队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待
#include <sys/socket.h>
int accept(
    int sockfd,
    struct sockaddr *cliaddr,
    socklen_t *addrlen
);
参数
    sockfd	:socket
    cliaddr	:用于存放客户端socket的地址结构
    addrlen:套接字地址结构体长度的地址
Return	
    已连接的socket

TCP应用层无法发送长度为0的数据报,因为为0表示这通讯结束。

案例代码

可以上述client联合使用,客户端连接服务器的端口要与服务器端绑定的端口一致。

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <fcntl.h>
int main(void)
{
    //1、创建一个tcp监听套接字
    int sockfd = socket(AF_INET,  SOCK_STREAM, 0);
    //2、使用bind函数给监听套接字绑定固定的ip和端口
    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(8080);
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(sockfd,(struct sockaddr *)&server, sizeof(server));
    //3、使用listen函数连接队列(主动变被动)  
    listen(sockfd, 10);  
    //4、使用accept函数从连接队列中提取已经完成的连接
    struct sockaddr_in client;
    bzero(&client,sizeof(client));
    socklen_t len = sizeof(client);
    //fd代表index客户端连接,client存储客户端的信息
    int fd = accept(sockfd, (struct sockaddr *)&client, &len);
    char ip[16] = "";
    inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);
    printf("client %s connected\n",ip);
    //5、获取客户端的请求,以及回应客户端
    char buf[128] = "";
    recv(fd, buf, sizeof(buf), 0);
    printf("client request is %s\n",buf);
    send(fd,"recv", strlen("recv"), 0);
    //6、关闭已连接套接字
    close(fd);
    //7、关闭监听套接字
    close(sockfd);
    return 0;
}

TCP并发服务器

可以同时服务多个客户端。TCP并发服务器本质还是TCP服务器,同时其又可以服务多个客户端。以下将提供两种方法:多进程与多线程

多线程

客户端正常退出不会有影响,但如果服务器端意外退出,再次运行bind的时候需要防止端口被占用(5~6分钟)。

#include <stdio.h>
#include <pthread.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <netinet/in.h>
#include <fcntl.h>
void *deal_client(void *sockfd);
int main(void)
{
    //1、创建TCP监听套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("CREATE SOCKET!!");
    }
    //端口复用
    int yes = 1;
    setsockopt(sockfd, SOL_SOCKET,  SO_REUSEADDR, &yes, sizeof(yes));
    //2、给socket绑定ip和端口信息
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(8088);
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    int result = bind(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (result == -1)
    {
        perror("bind");
    }
    // 3、调用listen,将sockfd调整主动变为被动
    listen(sockfd, 10);
    //4、提取建立完成的链接
    //accept调用一次只能接待一个客户端,需要反复调用accept
    while(1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int new_fd = accept(sockfd, (struct sockaddr *)&client, &len);
        //遍历客户端信息
        char  ip[16] = "";
        unsigned short port = ntohs(client.sin_port);
        inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);
        printf("client %s is connected %hu port\n", ip, port);
        //为每个客户端创建线程,单独的服务客户端
        pthread_t tid;
        pthread_create(&tid, NULL, deal_client, (void *)&new_fd);
        //线程分离
        pthread_detach(tid);
        
    }
    close(sockfd);
    return 0;
}
//TCP并发(并发服务器核心代码都不相同)
void *deal_client(void *sockfd)
{
    //通过arg获得已连接socket
    //给服务器发啥就回啥
    int fd = *(int *)sockfd;
    //服务器核心代码,处理客户端请求、
    while (1)
    {
        char data[128] = "";
        int len = recv(fd, data, sizeof(data), 0);
        if (!len){
            break;
        }
        send(fd, data, len, 0);
    }
    close(fd);
}

多进程

父子进程、资源独立、某个进程结束不会影响已有进程,服务器更加稳定,代价是多进程会浪费资源

#include <stdio.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
void deal_client(int sockfd);
int main(void)
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
    }
    int yes;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = htonl(INADDR_ANY);
    server.sin_port = htons(8088);
    bind(sockfd, (struct sockaddr *)&server, sizeof(server));
    listen(sockfd, 10);
    while (1)
    {
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int sock_new = accept(sockfd, (struct sockaddr *)&client, &len);
        unsigned short port = ntohs(client.sin_port);
        char ip[16] = "";
        inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, 16);
        printf("client ip %s connected at port %hu\n",ip,port);
        //子进程
        //使用fork后,会复制父进程的全部状态资源,注意下面四个关闭socket,描述符
        if (fork() == 0)
        {
            //关闭监听socket(进程中的,以防止与父进程冲突)
            close(sockfd);
            //服务于客户端的核心代码
            deal_client(sock_new);
            //关闭已连接socket(进程中的,以防止与父进程冲突)
            close(sock_new);
            _exit(-1);
        }
        else //父进程
        {
            //关闭父进程中的已连接socket
            close(sock_new);
        }
    }
    //关闭父进程中的监听socket
    close(sockfd);
    return 0;
}
void deal_client(int sockfd)
{
    while (1)
    {
        char buf[128] = "";
        int len = recv(sockfd, buf, sizeof(buf), 0);
        if (!len)
        {
            break;
        }
        //printf("the msg is %s\n",buf);
        send(sockfd, buf, len, 0);
    }
}

 

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我要出家当道士

打赏是不可能,这辈子都不可能

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值