【1】简单的TCP交互程序

相关函数释义

Socket

#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol); 
/*  功能:创建套接字
	domain:协议族
	type:套接字类型
	protocol:协议类型
	返回值:成功返回套接字描述符号,出错的时候返回-1
*/

domain现在能使用的类型如下,现在常用的类型是AF_INET。

AF_UNIX, AF_LOCAL  /* Local communication              unix(7) */
AF_INET            /* IPv4 Internet protocols          ip(7) */
AF_INET6           /* IPv6 Internet protocols          ipv6(7) */
AF_IPX             /* IPX - Novell protocols */
AF_NETLINK         /* Kernel user interface device     netlink(7) */
AF_X25             /* ITU-T X.25 / ISO-8208 protocol   x25(7) */
AF_AX25            /* Amateur radio AX.25 protocol */
AF_ATMPVC          /* Access to raw ATM PVCs */
AF_APPLETALK       /* AppleTalk                        ddp(7) */
AF_PACKET          /* Low level packet interface       packet(7) */
AF_ALG             /* Interface to kernel crypto API */

type套接字类型有定义如下:

SOCK_STREAM     /* Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission    mechanism may be supported. */
SOCK_DGRAM      /* Supports datagrams (connectionless, unreliable messages of a fixed maximum length). */
SOCK_SEQPACKET  /* Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire  packet  with  each input system call. */
SOCK_RAW        /* Provides raw network protocol access. */
SOCK_RDM        /* Provides a reliable datagram layer that does not guarantee ordering. */
SOCK_PACKET     /* Obsolete and should not be used in new programs; see packet(7). */

protocol协议类型有定义如下:

IPPROTO_IP /* IP传输协议 */
IPPROTO_TCP /* TCP传输协议 */
IPPROTO_UDP /* UDP传输协议 */
IPPROTO_STCP /* STCP传输协议 */
IPPROTO_TIPC /* TIPC传输协议 */

bind

#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*  功能:将本地协议地址与sockfd绑定
	sockfd:套接字描述符
	addr:一个指向sockaddr结构体类型的指针
	addrlen:sockaddr结构的长度
	返回值:成功返回0,失败返回-1
*/

通用的网络地址标识为sockaddr,为了使不同格式的地址能够传入到套接字函数,地址需要被强制转换成sockaddr类型。struct sockaddr的定义如下:

struct sockaddr {
	sa_family_t sa_family;
	char        sa_data[14];
}

Internet的地址定义在<netinet/in.h>,在IPV4因特网中(AF_INET)中,套接字的地址结构如下:

struct sockaddr_in {
	sa_family_t sin_family;   /*address family*/
	in_port_t sin_port;       /*port*/
	struct in_addr sin_addr;  /*address*/
};

struct in_addr {
	in_addr_t s_addr;
}

sockaddr和sockaddr_in二者长度一样,都是16个字节,即占用的内存大小是一致的,因此可以互相转化。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。


listen

#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*  功能:监听本地端口
	sockfd:套接字描述符
	backlog:连接请求队列的最大长度(一般由2到4)
	返回值:成功返回0,失败返回-1
*/

accept

#include <sys/socket.h> 
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
/*  功能:建立连接
	sockfd:套接字描述符
	addr:一个指向sockaddr结构体类型的指针
	addrlen:sockaddr结构的长度
	返回值:成功返回0,失败返回-1
*/

connect

#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
/*  功能:建立与指定socket的连接
	sockfd:套接字描述符
	addr:一个指向sockaddr结构体类型的指针
	addrlen:sockaddr结构的长度
	返回值:成功返回0,失败返回-1
*/

recvmsg/sendmsg
socket发送和接收的函数有如下几对:recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg。

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

其他
计算机处理器的结构不同,字节序可能不同,分为大端可小端,大端模式下高地址存储低字节,小端模式相反。在不同的计算机之间通信,那么需要考虑字节序。网络协议指定了字节序称之为网络字节序,在利用socket进行通信时,应用程序可能需要在网络字节序与主机字节序之间进行转换,以确保交换格式化数据时字节序不会出现问题。TCP/IP使用大端字节序,在<arpa/inet.h>中定义了4个网络字节序转换函数如下:

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostint32);    /*将32位的数据从主机字节序转换为网络字节序*/
uint16_t htons(uint16_t hostint16);   /*将16位的数据从主机字节序转换为网络字节序*/
uint32_t ntohl(uint32_t netint32);     /*将32位的数据从网络字节序转换为主机字节序*/
uint16_t ntohs(uint16_t netint16);    /*将16位的数据从网络字节序转换为主机字节序*/

网络IP地址的转换函数如下:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
in_addr_t inet_addr(const char *cp); /* 将字符串形式的IP地址转换为网络字节顺序的整型值 */
in_addr_t inet_network(const char *cp);
char *inet_ntoa(struct in_addr in); /* 网络字节顺序的整型值转换为字符串形式的IP地址 */
struct in_addr inet_makeaddr(in_addr_t net, in_addr_t host);
in_addr_t inet_lnaof(struct in_addr in);
in_addr_t inet_netof(struct in_addr in);

客户端程序

Client端步骤如下

  1. 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
  2. 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
  3. 调用connet函数和指定的服务器建立一个TCP连接
  4. 使用read/write来与服务器通信

代码
客户端通过终端输入字串,发送到服务器端,然后从服务器端接收字串,输出到终端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

static void do_msg_process(int socketfd) {
    printf("do_msg_process enter!\n");
    int size = 0;
    char read_buffer[512] = {0};
    char write_buffer[1024] = {0};

    while(true) {
        memset(read_buffer, 0x00, sizeof(read_buffer));
        memset(write_buffer, 0x00, sizeof(write_buffer));

        printf("input:> ");
        fgets(write_buffer, 1024, stdin);

        size = write(socketfd, write_buffer, strlen(write_buffer));

        size = read(socketfd, read_buffer, 1024);
        printf("server say: %s\n", read_buffer);
    }
    printf("do_msg_process exit!\n");
}

int main(int argc, char const *argv[]) {
    // 利用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
    int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketfd < 0) {
        perror("socket create failed!\n");
        return 0;
    }

    // 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
    struct sockaddr_in c_addr;
    memset(&c_addr, 0x00, sizeof(struct sockaddr_in));
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(6666);
    inet_pton(AF_INET, "127.0.0.1", &c_addr.sin_addr);

    // connet将和指定的服务器建立一个TCP连接
    int result = connect(socketfd, (struct sockaddr*)&c_addr, sizeof(struct sockaddr_in));
    if (result < 0) {
        perror("connect failed!\n");
        return 0;
    }

    // 处理消息
    do_msg_process(socketfd);

    close(socketfd);
    return 0;
}


服务端程序

Server端步骤如下

  1. 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
  2. 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
  3. 调用bind函数绑定所创建的套接字
  4. 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
  5. 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
  6. 使用read/write与客户端通讯
  7. 调用close来关闭与客户端的连接

代码
服务器端接收客户端字串,然后将字串增加[SERVER]:头,再返回给客户端。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

static void do_msg_process(int accsocfd) {
    printf("do_msg_process enter!\n");
    int size = 0;
    char read_buffer[512] = {0};
    char write_buffer[1024] = {0};

    while (true) {
        memset(read_buffer, 0x00, sizeof(read_buffer));
        memset(write_buffer, 0x00, sizeof(write_buffer));

        size = read(accsocfd, read_buffer, 1024);
        printf("client say: %s\n", read_buffer);

        snprintf(write_buffer, 1024, "%s%s", "[SERVER]: ", read_buffer);
        size = write(accsocfd, write_buffer, strlen(write_buffer));
    }
    printf("do_msg_process exit!\n");
}

int main(int argc, char const *argv[]) {
    // 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
    int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketfd < 0) {
        perror("socket create failed!\n");
        return 0;
    }

    // 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
    struct sockaddr_in s_addr;
    memset(&s_addr, 0x00, sizeof(struct sockaddr_in));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(6666);
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 调用bind函数绑定所创建的套接字
    bind(socketfd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr));

    // 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
    listen(socketfd, 10);

    while (true) {
        // 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
        int accsocfd = accept(socketfd, NULL, NULL);
        // 处理消息
        do_msg_process(accsocfd);
        // 调用close来关闭与客户端的连接
        close(accsocfd);
    }

    close(socketfd);
    return 0;
}


运行结果

客户端
在这里插入图片描述
服务端
在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值