TCP通讯

稳定连接-打电话

一、实现步骤

1.创建socket

2.初始化协议地址簇

3.绑定(服务端需要)

4.监听(服务端需要)

5.接收连接

6.开始通讯

7.通讯完成关闭socket

二、socket套接字

1.相关结构

struct sockaddr_in
{
    short int sin_family;       //协议簇
    unsigned short int sin_port; //端口号
    struct in_addr sin_addr;    //IP地址,设置时使用sin_addr.s_addr
    unsigned char sin_zero[8];   //保留空字节
}

2.相关函数

1.创建套接字

1.创建套接字
int socket(int domain.int type,int protocol);
2.domain-域名
AF_INET 表示IPV4地址;
3.type-类型
SOCK_STREAM 流式套接字;
SCOK_DGRAM 数据报套接字;
4.protocol-协议编号;
IPPROTO_TCP 表示tcp协议
​

2.int atoi(const char* str)

#include<stdlib>

功能:将字符串str转换成一个整数并返回一个结果。参数str以数字开头,当函数从str中读到非数字字符则结束转换并将结果返回。

3.绑定-服务端使用

int bind(int sockfd, const struct sockaddr *, socklen_t addrlen);
//把一个本地协议地址赋予给一个套接字

4.监听-服务端使用

int listen(int sockfd, int backlog);
​
netstat -aptn | grep 8585 //查看是否在监听
sudo lsof -i:8585//查看被监听的端口号

5.接收连接——服务端

int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
//接收一个套接字中已建立的连接(服务端)

6.连接——客户端

int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
//这里是根据服务端和客户端的端口号来判断的,端口号一样才能进行连接

7.发送数据

int send( SOCKET s, const char FAR *buf, int len, int flags );

8.接收数据

int recv( SOCKET s, char FAR *buf, int len, int flags );

9.主机字节序转网络字节序

u_short htons(u_short hostshort);
//因为主机字节序是小端存储,而网络字节序是大端存储,所以需要转换

10.点分十进制字符串转网络字节序的整数 ip

unsigned long inet_addr(const char FAR* cp);

11.将网络传输的二进制数值转化为成点分十进制的ip地址

char *inet_ntoa(struct in_addr in);

12.将一个无符号短整型数从网络字节顺序转换为主机字节顺序

uint16_t ntohs(uint16_t netshort);

三、代码及调试

使用pthread.h库出现问题

解决方法

包含了线程的头文件<pthread.h>,可是编译的时候却报错“对pthread_create未定义的引用“,是因为pthread库不是Linux系统默认的库,连接时需要使用库libpthread.a,所以在使用pthread_create创建线程时,在编译中要加-lpthread参数:gcc -g sererver.c -o tcpServer -lpthread 加上这个以后编译成功!

1.服务端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include <unistd.h>//关闭套接字相关头文件 close()
void thread_run(void* arg);
​
​
int main(int argc,char* argv[])
{
    //应为系统会自动传入当前文件的路径地址,所以加上IP地址和端口号,argc=3,argv[0]:可执行文件名
    if(argc!=3)//调用可执行程序时,必须传递IP地址和端口号,argv[1]:IP地址,argv[2]:端口号
    {
       printf("please use : %s [IP] [port]\n",argv[0]);
       return 1;
    }
    //创建套接字,一个句柄
    int sock;
    sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //创建套接字失败
    if(sock<0)
    {
        perror("creat socket errno!\n");
        return 1;
    }
​
    //初始化协议簇地址
    //创建服务器的sockaddr_in
    struct sockaddr_in local;
    //初始化协议簇
    local.sin_family=AF_INET;//IPV4的地址
    //初始化端口号
    local.sin_port=htons(atoi(argv[2]));//主机字节序是大端存储,要转为网络字节序的小端存储
    //初始化IP地址
    local.sin_addr.s_addr=inet_addr(argv[1]);//,IP地址是一个点分十进制字符串,需要转换成网络字节序的整数ip
​
    //服务端需要绑定
    //将socket和协议簇绑定,也就是将端口号和IP地址加入到socket里面去
    //bind-把一个本地协议地址簇赋予给一个套接字
    if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
    {
        //输出错误信息
        perror("bind errno!\n");
        //关闭socket
        close(sock);
        return 1;
    }
​
    //服务端需要监听
    if(listen(sock,10))
    {
        perror("listen errno!\n");
        close(sock);
        return 1;
    }
    //等待接收
    printf("bind and listen success! wait accept ... \n");
​
    //接收客户端的连接
    //客户端的sockaddr_in
    struct sockaddr_in clientSock;
    //求出这个sockaddr_in的大小
    socklen_t len=sizeof(clientSock);
    //死循环来接收连接及数据-阻塞接收
    while(1)
    {
        //accept是阻塞函数
        //接收客户端套接字中已建立的连接(服务端)
        int fd=accept(sock,(struct sockaddr*)&clientSock,&len);
        //接收失败
        if(fd<0)
        {
            perror("accept errno!\n");
            close(sock);
            return 1;
        }
​
        //接收到连接
         printf("get connect\nip is : %s\nport is : %d\n",
            inet_ntoa(clientSock.sin_addr),ntohs(clientSock.sin_port));
        
        //6、通讯(用多线程来演示)
        pthread_t id;
        pthread_create(&id,NULL,thread_run,(void *)fd);
        pthread_detach(id);
​
    }
    //关闭
    close(sock);
​
​
    return 0;
}
void thread_run(void* arg)
{
    printf("create a new thread\n");
    //fd表示连接的socket句柄,通过该变量可以进行网络通讯
    int fd = (int)arg;
    //接收数据的缓冲区
    char buf[1024] = {};
​
    //开始通讯
    while(1)
    {
        //将buf全部初始化为0
        memset(buf,0,sizeof(buf));
        //接收数据
        int ret=recv(fd,buf,sizeof(buf)-1,0);
        //接收数据成功
        if(ret > 0)
        {
            buf[ret] = '\0';
            printf("client say : %s\n",buf);
        }
​
        memset(buf,0,sizeof(buf));
        printf("please Enter:");
        //清空缓冲区数据
        fflush(stdout);
        //输入要发送给客户端的数据
        scanf("%s",buf);//scanf也是一个阻塞函数
        ret = strlen(buf);
        //6-2、发送数据
        if(ret > 0)
        {
            send(fd,buf,strlen(buf) + 1,0);
        }
​
    }
}

2.客户端

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>//socket相关头文件
#include<netinet/in.h>//sockadd_in相关头文件
#include<arpa/inet.h>//大端转小端头文件
#include<pthread.h>//线程相关头文件
#include <unistd.h>//关闭套接字函数相关头文件 close()
​
int main(int argc,char* argv[])
{
    if(argc!=3)//开始执行时,必须输入IP地址和端口号
    {
         printf("please use : %s [IP] [port]\n",argv[0]);
         return 1;
    }
​
    //创建客户端socket
    int sock;
    sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //判断socket是否创建成功
    if(sock<0)
    {
        perror("creat sock errno!\n");
        return 1;
    }
​
    //初始化协议簇
    struct sockaddr_in client;
    client.sin_family=AF_INET;
    client.sin_port=htons(atoi(argv[2]));
    client.sin_addr.s_addr=inet_addr(argv[1]);
​
    //客户端不需要监听和绑定,直接连接即可
    int ret=connect(sock,(struct sockaddr*)&client,sizeof(client));
    //判断是否连接成功
    if(ret<0)
    {
        perror("connect errno!\n");
        close(sock);
        return 1;
    }
    //连接成功
    printf("connect success!\n");
​
    //接收数据的数组
    char buf[1024] = {};
    //开始通讯
    while(1)
    {
        memset(buf,0,sizeof(buf));
        printf("please Enter:");
        fflush(stdout);
​
        //输入通讯内容
        scanf("%s",buf);
        ret = strlen(buf);
        //发送数据给服务端
        if(ret > 0)
        {
            //发送给服务端
            send(sock,buf,strlen(buf) + 1,0);
            //接收数据
            int ret = recv(sock,buf,sizeof(buf) - 1,0);//0表示一次收发
            if(ret > 0)
            {
                //忽略大小写比较字符串,比较参数3表示的n个字符
                if(strncasecmp(buf,"quit",4) == 0)
                {
                    printf("quit\n");
                    break;
                }
                buf[ret] = '\0';
                printf("client say : %s\n",buf);
            }
        }
    }
        //关闭socket通道
        close(sock);
}

3.gdb

因为调用可执行程序时需要输入参数,所以使用

gdb set args //设置多个参数

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值