基本TCP套接字编程


基本流程

服务器:

    1. 创建套接字socket()
    1. 填充服务器网络信息结构体(上节记录了)
    1. 将套接字与服务器网络信息结构体绑定bind()
    1. 将服务器设置为监听状态listen()
    1. 阻塞等待客户端的连接accept()
    1. read()/write(), recv()/send(), recvfrom()/sendto()等函数通信
      客户端:
    1. 创建套接字socket()
    1. 填充网络服务器信息结构体
    1. connect()给服务器发送请求
    1. 进行通信
    1. close() 关闭

相关函数

socket()函数

#include <sys/socket.h>

int socket(int family, int type, int protocol);

创建一个套接字,返回文件描述符,失败返回-1.
family指明协议族可以为以下几种:

  • AF_INET:IPv4协议
  • AF_INET6:IPv6协议
  • AF_LOCAL:Unix域协议
  • AF_ROUTE:路由套接字
  • AF_KEY:秘钥套接字

type指明套接字的类型,可以是

  • SOCKET_STREAM:字节流套接字
  • SOCKET_DGRAM:数据报套接字–UDP
  • SOCKET_SEQPACKET:有序分组套接字
  • SOCKET_RAW:原始套接字

protocol:附加协议,传0表示不需要其他协议,一些协议有

  • IPPROTO_TCP: TCP传输协议
  • IPPROTO_UDP: UDP传输协议
  • IPPROTO_SCTP: SCTP传输协议

connect()函数

函数原型

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

功能是TCP客户端建立与TCP服务器的连接
sockfd是socket()函数返回的套接字描述符
servaddr是包含要连接服务器网络信息的结构体
addrlenservaddr的大小
在调用connect前不必非得调用bind()函数,因为操作系统内核会确定源IP地址和临时端口号

该函数建立成功后会返回0, 失败返回-1。

bind()函数

函数原型

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddy, socklen_t addrlen);

功能是将客户端套接字与网络信息结构体绑定
sockfd同上,是socket()函数返回的套接字描述符
myaddr是用户网络信息结构体指针
addrlen网路信息结构体的大小

listen()函数

函数原型

#include <sys/socket.h>
int listen(int sockfd, int backlog);

sockte()函数创建套接字时,被设定为一个主动套接字,是一个将调用connect()函数发起连接请求的客户套接字。listen把一个未连接的套接字转换成一个被动套接字,指示内核应接受指向该套接字的连接请求。会使一个未连接的套接字由CLOSED状态转换成LISTEN状态。
参数:
sockfd:socked文件描述符
backlog:允许同时连接的客户端的个数,和内核为监听套接字维护的两个队列有关,两个队列如下:
未完成连接队列:服务器在三次握手中受到第一个握手信号后进入SYN——RCVD状态,为这样的套接字维护的队列
已完成连接队列:完成三次握手的套接字所维护的队列。

accept()函数

函数原型

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

服务器调用该函数,用于从已完成连接队列队头返回下一个已完成连接,如果已完成连接队列为空,则进程进入睡眠状态。
参数
sockfd:同上
cliaddr :用来返回已连接的对端(客户)的协议地址。
addrlen:addr的大小

服务器调用该函数后,可能会返回错误值或一个全新的套接字描述符,参数中的第一个参数一般称为监听套接字,调用函数后,返回一个新的套接字称为已连接套接字。系统中常常仅创建一个监听套接字进程,而当三次握手完成后,系统会为每个连接建立一个已连接套接字。

函数使用尝试

服务器端

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

int main(int argc, char *argv[])
{
	if(argc < 3)
	{
		fprintf(stderr, "Usage: %s <ip> <port>\n", argv[0]);
		exit(1);
	}
	int sockfd;
	struct sockaddr_in serveraddr, clientaddr;
	socklen_t addrlen = sizeof(serveraddr);
	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
	{
		printf("socket error\n");
		exit(1);
	}
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	serveraddr.sin_port = htons(atoi(argv[2]));
	if(bind(sockfd, (struct sockaddr*)&serveraddr, addrlen) == -1)
	{
		printf("bind error\n");
		exit(1);
	}
	if(listen(sockfd, 5) == -1)
	{
		printf("listen error\n");
		exit(1);
	}
	if(accept(sockfd, (struct sockaddr*)&clientaddr, &addrlen) == -1)
	{
		printf("accept error");
		exit(1);
	}
	printf("client %s : %d connected\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
	return 0;
}

客户端

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

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        fprintf(stderr, "usage: %s <ip> <port>\n", argv[0]);
        exit(1);
    }
    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));
    connect(sockfd, (struct sockaddr*)&serveraddr, addrlen);
    return 0;
}

客户端服务器通信相关函数

recv()/send()函数

函数原型

#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

实现接收数据,返回成功接收的字节数,失败则为-1。
参数
sockfd:文件描述符,在客户端是socket()函数的返回值,在服务器是accept()的返回值
buf:保存接收到的数据
len:理论要接收的字节数
flags:标志位,0阻塞,MSG_DONTWAIT非阻塞

函数原型

#include <sys/socket.h>
ssize_t send(int sockfd, const void* buf, size_t len, int falgs)

实现发送数据,返回成功发送的字节,失败返回-1。
参数
sockfd:文件描述符,在客户端是socket()函数的返回值,在服务器是accept()的返回值
buf:要发送的数据
len:理论要发送的字节数
flags:标志位,0阻塞,MSG_DONTWAIT非阻塞

recv()和send()函数使用

服务器

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <string.h>
#include <cstring>

using namespace std;

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        printf("please input <ip> <port>\n");
        exit(1);
    }
    int sockfd;
    struct sockaddr_in serveraddr, clientaddr;
    socklen_t addrlen = sizeof(serveraddr);
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_port = htons(atoi(argv[2]));

    bind(sockfd, (struct sockaddr*)&serveraddr, addrlen);
    listen(sockfd, 5);
    int acceptfd;
    acceptfd = accept(sockfd,(struct sockaddr*)&clientaddr, &addrlen);
    printf("client %s : %d connected\n",inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    ssize_t bytes;
    char buf[100] = {0};
    while(1)
    {
        bytes = recv(acceptfd, buf, 100, 0);
        if(bytes == -1)
        {
            printf("recv error\n");
        }
        else if(bytes == 0)
        {
            printf("client exit\n");
            acceptfd = accept(sockfd,(struct sockaddr*)&clientaddr, &addrlen);
        }
        if(strcmp(buf, "quit") == 0)
        {
            printf("client exit\n");
            acceptfd = accept(sockfd,(struct sockaddr*)&clientaddr, &addrlen);
        }
        printf("%s-%d: %s\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buf);
        strcat(buf, "^_^");

        if(send(acceptfd, buf, 100, 0) == -1)
        {
            printf("send error\n");
        }
    }
    return 0;
}

客户端

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

int main(int argc, char *argv[])
{
    if(argc < 3)
    {
        printf("please input <ip> <port>\n");
        exit(1);
    }
    int sockfd;
    struct sockaddr_in serveraddr;
    socklen_t addrlen = sizeof(serveraddr);
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    connect(sockfd, (struct sockaddr*)&serveraddr, addrlen);
    char buf[100] = {0};
    while(1)
    {
        fgets(buf, 100, stdin);
        buf[strlen(buf) - 1] = '\0';

        if(send(sockfd, buf, 100, 0) == -1)
        {
            printf("send error\n");
        }

        if(strcmp(buf, "quit") == 0)
        {
            printf("client exit\n");
            exit(0);
        }

        memset(buf, 0, 100);

        if(recv(sockfd, buf, 100, 0) == -1)
        {
            printf("recv error\n");
        }

        printf("server: %s\n", buf);
    }
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

up-to-star

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值