TCP服务器端、客户端模式初文

本文介绍了TCP客户端和服务器的实现步骤,包括创建套接字、设置网络通信地址、服务器绑定与客户端连接、消息收发及关闭套接字。详细讲解了socket函数、bind、connect、recv和send等关键函数的使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

TCP客户端模式

STEP1.创建套接字,使用socket函数

套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。

int socket(int domain, int type, int protocol);
//Linux下失败返回-1

参数说明
domain:协议域,又称协议族(family)。常用的协议族有AF_INET、AF_INET6、AF_LOCAL(或称AF_UNIX,Unix域Socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
type:指定Socket类型。常用的socket类型有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等。流式Socket(SOCK_STREAM)是一种面向连接的Socket,针对于面向连接的TCP服务应用。数据报式Socket(SOCK_DGRAM)是一种无连接的Socket,对应于无连接的UDP服务应用。
protocol:指定协议。常用协议有IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
注意:1.type和protocol不可以随意组合,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当第三个参数为0时,会自动选择第二个参数类型对应的默认协议。
2.WindowsSocket下protocol参数中不存在IPPROTO_STCP
返回值:如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET(Linux下失败返回-1)。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。

STEP2.准备通信地址,网络通信地址
#include <netinet/in.h>
struct sockaddr_in{
shortsin_family;/*Addressfamily一般来说AF_INET(地址族)PF_INET(协议族)*/

unsigned shortsin_port;/*Portnumber(必须要采用网络数据格式,普通数字可以用htons()函数转换成网络数据格式的数字)*/

structin_addrsin_addr;/*Internetaddress*/

unsigned charsin_zero[8];/*Samesizeasstructsockaddr没有实际意义,只是为了跟SOCKADDR结构在内存中对齐*/
};
STEP3.(1)如果是服务器绑定网络服务器使用bind
#include <sys/types.h>   #include <sys/socket.h>
int bind(int sockfd, struct sockaddr * my_addr, int addrlen);

函数说明:bind()用来设置给参数sockfd 的socket 一个名称. 此名称由参数my_addr 指向一sockaddr 结构,对于不同的socket domain 定义了一个通用的数据结构

(2)如果是客户端连接服务器主机的网络地址 connect
#include <sys/types.h>   #include <sys/socket.h>
int connect(int sockfd, struct sockaddr * serv_addr, int addrlen);

函数说明:connect()用来将参数sockfd 的socket 连至参数serv_addr 指定的网络地址. 结构sockaddr请参考bind(). 参数addrlen 为sockaddr 的结构长度.

返回值:成功则返回0, 失败返回-1, 错误原因存于errno 中.
错误代码:EBADF 参数fd 非有效的文件描述词或该文件已关闭.

附加说明:虽然在进程结束时, 系统会自动关闭已打开的文件, 但仍建议自行关闭文件, 并确实检查返回值.

STEP4.(1)如果是服务器就循环接收客户消息
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

参数
fd: 将要读取数据的文件描述词。
buf: 所读取到的数据的内存缓冲。
count: 需要读取的数据量。

返回说明
成功执行时,返回所读取的数据量。失败返回-1,errno被设为以下的某个值
EAGAIN:打开文件时设定了O_NONBLOCK标志,并且当前没有数据可读取
EBADF:文件描述词无效,或者文件不可读
EFAULT:参数buf指向的空间不可访问
EINTR:数据读取前,操作被信号中断
EINVAL:一个或者多个参数无效
EIO:读写出错
EISDIR:参数fd索引的时目录

(2)如果是客户端发送消息到服务器
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

参数
fd:要进行写操作的文件描述词。
buf:需要输出的缓冲区
count:最大输出字节计数

STEP5.关闭socket
#include <unistd.h>
int close(int fd);

函数说明:当使用完文件后若已不再需要则可使用 close()关闭该文件, 二close()会让数据写回磁盘, 并释放该文件所占用的资源. 参数fd 为先前由open()或creat()所返回的文件描述词.
返回值:若文件顺利关闭则返回0, 发生错误时返回-1.

错误代码:EBADF 参数fd 非有效的文件描述词或该文件已关闭.

附加说明:虽然在进程结束时, 系统会自动关闭已打开的文件, 但仍建议自行关闭文件, 并确实检查返回值.

函数参考:http://c.biancheng.net/cpp/
http://blog.youkuaiyun.com/yylklshmyt20090217/article/details/4807390

参考实现代码:

MY_CLIENT

#define N 100//单次最大访问信息 
int main(int argc, char const *argv[]) {
//Step START :from the user obtain the IP PORT MESSAGE
//对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP(简单文件传送协议)服务器的UDP端口号都是69。任何TCP/IP实现所提供的服务都用知名的1~1023之间的端口号。 //Step START
    if (argc != 4) {
        printf("Usage_Command  Please enter :ip port message(max:100 bytes)\n");
        exit(-1);
    } else if (atoi(argv[2]) < 0) {
        printf("Wrong port!\n");
        exit(-1);
    } else if (strlen(argv[3]) > N) {
        printf("Message too long!\n");
        exit(-1);
    }
    int socket_fd, size;
    struct sockaddr_in servaddr;
    char ip[20], port[10], buf[100];
    strcpy(ip, argv[1]);
    strcpy(port, argv[2]);
    strcpy(buf, argv[3]);
    //Step 1
    //AF 表示ADDRESS FAMILY 地址族  PF 表示PROTOCOL FAMILY 协议族
    //在windows中AF_INET与PF_INET完全一样;而在Unix/Linux系统中,在不同的版本中这两者有微小差别.对于BSD,是AF,对于POSIX是PF. 理论上建立socket时是指定协议,应该用PF_xxxx,设置地址时应该用AF_xxxx。当然AF_INET和PF_INET的值是相同的
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == socket_fd) {
        perror("Create socket failed.\n");
        exit(-1);
    }
    printf("Create a socket successfully.\n");
    //Step 2
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(port));//atoi->字符转数字 
    servaddr.sin_addr.s_addr = inet_addr(ip);
    //Step 3
    int aws = connect(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 
    if (-1 == aws) {
        printf("Connect error.\n");
        exit(-1);
    }
    //Step 4
    aws = write(socket_fd, buf, strlen(buf));
    if (-1 == aws) {
        printf("Fail\n");
        exit(-1);
    } else
        printf("Success\n");
    //Step 5
    close(socket_fd);
    //Step END
    return 0;
}

MY_SERVER

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char const *argv[]) {
    //Step START
    if (3 != argc) {
        printf("Usage_Command  Please enter :ip port\n");
        exit(-1);
    } else if (atoi(argv[2]) < 0) {
        printf("Wrong port!\n");
        exit(-1);
    }
    int socket_fd;
    struct sockaddr_in servaddr;
    char buf[100], ip[20], port[10];
    strcpy(ip, argv[1]);
    strcpy(port, argv[2]);
    //Step 1
    int socket_fd = socket(AF_INET, SOCK_DGRAM, 0)
    if (-1 == socket_fd) {
        perror("Create socket failed.\n");
        exit(-1);
    }
    printf("Create a socket successfully.\n");
    //Step 2
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(atoi(port));
    servaddr.sin_addr.s_addr = inet_addr(ip);
    //Step 3
    int res = bind(socket_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
    if (-1 == res) {
        printf("bind error\n");
        exit(-1);
    }
    //Step 4
    while (1) {
        memset(buf, '\0', sizeof(buf));
        res = read(socket_fd, buf, 100);
        if (-1 == res) {
            printf("read error\n");
            exit(-1);
        } else
            printf("Received:%s\t[%dbyte(s)]\n", buf, strlen(buf));
    }
    //Step 5
    close(socket_fd);
    //Step END
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值