socket实现TCP/UDP通信协议设计

基于套接字编程的TCP/UDP通信协议设计

tcp提供客户与服务器的连接,一个TCP客户建立一个与服务器的连接,并与能够服务器交换数据,然后终止连接,提供可靠性。当TCP向另一端发送数据时,它要求对端返回一个确认,如果确认没有收到,TCP自动重传数据,并等待更长时间。

UDP是一个简单的传输层协议,提供无连接的服务,不需要与客户端建立连接。UDP客户端与服务器不必存在长期的关系,缺乏可靠性。协议不保证分组能够最终到达目的地,不保证各个分组按先后顺序跨网络保持不变,也不保证每个分组只到达一次。

Socket编程通常称为“套接字”,用于描述IP地址,端口和系统资源,是通信链的句柄。其使用与文件操作类似。
在这里插入图片描述
在这里插入图片描述

tcp_receiver.c文件代码

#include "net_exp.h"

int main(int argc, char **argv) {

    /* 建立服务端套接字 */
    int server_sockfd;
    if ((server_sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0) {               /*(协议族、网络类型,数据传送方式,协议0)*/
        perror("socket error");
        return 1;
    }

    /* 监听端口 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);                                       /*(s,c,n)将s所指地址前n比特用常数c填充*/
    server_addr.sin_family =AF_INET;                   /*地址族AF_INET*/
    server_addr.sin_addr.s_addr =htonl(INADDR_ANY);                              /*ip地址*/
    server_addr.sin_port =htons(TCP_SERVER_PORT);                                 /*端口号*/
    
    if (bind(server_sockfd,(struct sockaddr_in*)&server_addr,sizeof(server_addr)) == -1) {                       
           /*(sockfd,指针指向要绑定给sockfd的协议地址,长度)*/
        perror("bind error");
        return 1;
    }

    if (listen(server_sockfd,1) == -1) {                                                           /*(sockfd,指定此套接口排队最大连接数目)*/
        perror("listen error");
        return 1;
    };

    /* 建立tcp连接 */
    int client_sockfd;
    struct sockaddr_in client_addr;
    unsigned int client_addr_len = sizeof(struct sockaddr_in);
    if ((client_sockfd = accept(server_sockfd,(struct sockaddr_in*)&client_addr,&client_addr_len)) == -1) {
       /*(sockfd,客户端协议地址信息,长度)*/
        perror("accept error");
        return 1;
    }
    printf("accept client %s:%d\n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port);

    /* 接收数据 */
while(1){
    size_t pkt_len;
    char recv_buf[TCP_BUF_LENGTH];
    pkt_len = recv(client_sockfd,recv_buf,sizeof(recv_buf),0);                         /*(socktfd,缓冲区地址,长度,标志)*/

    if (pkt_len == -1) {
        perror("recv error");
        return 1;
    }

    if (pkt_len == 0) {
        /* 连接被远端关闭 */
        printf("finish\n");
        return 0;
    }


    /* 输出接收到的信息 */
    recv_buf[pkt_len] = '\0';
    printf("[TCP RECEIVER] receive msg[%d bytes]\n", pkt_len);
    printf("\t%s\n", recv_buf);

    /* 发送信息 */
    
    
    char xia[255];
    gets(xia);
  
    if (send(client_sockfd,xia,sizeof(xia),0) == -1) {                                 /*(socktfd,缓冲区地址,长度,标志)*/
     perror("send error");
      return 1;
    }
}
    /* 关闭套接字 */
    close(client_sockfd);                                  /*(socktfd)*/
    close(server_sockfd);
    return 0;
}

tcp_sender.c文件代码

//tcp.sender
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int sockfd;
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 建立tcp连接 */
    struct sockaddr_in server_addr;
    memset(&server_addr,sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr =inet_addr("192.168.245.128");
    server_addr.sin_port =htons(TCP_SERVER_PORT);

    if (connect(sockfd,(struct sockaddr_in*)&server_addr,sizeof(server_addr))) {
        perror("connect error");
        return 1;
    }

    /* 发送数据 */
while(1){
    char msg[255];
    gets(msg);
    send(sockfd,msg,strlen(msg)+1,0);

    /* 接收数据 */
    char recv_buf[TCP_BUF_LENGTH];
    size_t pkt_len = 0;
    pkt_len = recv(sockfd,recv_buf,sizeof(recv_buf),0);

    if (pkt_len == -1) {
        perror("recv error");
        return 1;
    }

    if (pkt_len == 0) {
        /* 连接被远端关闭 */
        printf("finish\n");
        return 0;
    }

    /* 输出接收到的信息 */
    recv_buf[pkt_len] = '\0';
    printf("[TCP SENDER] receive echo msg[%d bytes]\n", pkt_len);
    printf("\t%s\n", recv_buf);
}
    /* 关闭套接字 */
    close(sockfd);

    return 0;
}

面向连接的Client/Server结构中,服务器首先启动,通过调用socket()建立一个套接口,然后调用bind()将该套接口和本地网络地址联系在一起,再调用listen()使套接口做好侦听准备。并规定它的请求队列长度。之后调用accept()来接收连接。客户在建立套接口后就可以调用connect()和服务器建立连接。一旦连接建立,客户机就可以与服务器之间通过调用send()和recv()来发送和接收数据。最后,传输完成后,双方调用close()关闭套接口。

udp_receiver.c文件代码

//udp.receiver
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int sockfd;
    if ((sockfd = socket(AF_INET,SOCK_DGRAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 绑定端口 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(UDP_SERVER_PORT);

    if (bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr)) == -1) {
        perror("bind error");
        return 1;
    }

    /* 接收数据 */
    struct sockaddr_in client_addr;
    int client_addr_len;
    char recv_buf[UDP_BUF_LENGTH];
    size_t pkt_len;
    while (1) {
        memset(recv_buf, sizeof(recv_buf), 0);
        client_addr_len = sizeof(client_addr);
        pkt_len = recvfrom(sockfd,recv_buf,sizeof(recv_buf)+1,0,(struct sockaddr*)&client_addr,client_addr_len);
        recv_buf[pkt_len] = '\0';
        printf("[UDP_RECEIVER] receive msg[%d bytes]\n", pkt_len);
        printf("\t%s\n", recv_buf);
    }

    /* 关闭套接字 */
    close(sockfd);

    return 0;
}

udp_sender.c文件代码

//udp.sender
#include "net_exp.h"
int main(int argc, char **argv) {

    /* 建立套接字 */
    int socket_fd;
    if ((socket_fd = socket(AF_INET,SOCK_DGRAM,0)) == -1) {
        perror("socket error");
        return 1;
    }

    /* 发送数据 */
    struct sockaddr_in server_addr;
    memset(&server_addr, sizeof(server_addr),0);
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr =inet_addr("192.168.245.128");
    server_addr.sin_port = htons(UDP_SERVER_PORT);

    int counter = 0;
    char send_buf[UDP_BUF_LENGTH];

    while (1) {
        memset(send_buf, sizeof(send_buf), 0);
        printf("sending data packet with #: %d\n", counter);
        sprintf(send_buf, "data packet with #: %d.", counter);
        sendto(socket_fd,send_buf,sizeof(send_buf),0,(struct sockaddr*)&server_addr,sizeof(server_addr));

        counter++;
        if (counter > 10)
            break;

        sleep(1);
    }

    /* 关闭套接字 */
    close(socket_fd);

    return 0;
}

在无连接的Client/Server结构中,服务器使用socket()和bind()建立和联系socket。由于此时socket是无连接的,服务器使用recvfrom()从socket接口接收数据。客户端也调用bind()而不调用connect()。因为并未在两个端口建立点到点的连接,因此sendto()要求程序提供一个参数指明目的地址信息。secvfrom()不需要建立连接,它对到达相连接的协议端口的任何数据做出响应。取出数据报的同时,它将保存发送此数据包的进程的网络地址及包本身。

头文件内容:

#ifndef NETEXP_H
#define NETEXP_H


#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include <pthread.h>


#define		TCP_SERVER_ADDRESS		"127.0.0.1"
#define 	TCP_SERVER_PORT			8001
#define		TCP_BUF_LENGTH			1000
#define 	CONN_NUM			10

#define		UDP_SERVER_ADDRESS		"127.0.0.1"
#define		UDP_SERVER_PORT			8002 
#define		UDP_BUF_LENGTH			1000

#define		RDT_SERVER_ADDRESS		"127.0.0.1"
#define		RDT_SEND_LFILE_NAME		"data_long_send.mp4"
#define 	RDT_RECV_LFILE_NAME		"data_long_recv.mp4"
#define		RDT_SEND_SFILE_NAME		"abc_send.pdf"
#define 	RDT_RECV_SFILE_NAME		"abc_recv.pdf"
#define		RDT_ACK_MSG				"SUCC"
#define		RDT_NACK_MSG			"FAIL"		
#define 	RDT_RECV_PORT			8003
#define		RDT_SEND_PORT			8004
#define		RDT_BEGIN_SEQ			1
#define		RDT_SENDWIN_LEN			10

#define		RDT_PKT_LOSS_RATE		10
#define		RDT_TIME_OUT			50000
#define		RDT_HEADER_LEN			(4 + 4)
#define 	RDT_DATA_LEN			1000
#define		RDT_PKT_LEN			( RDT_DATA_LEN + RDT_HEADER_LEN )

#define		RDT_CTRL_BEGN			0
#define		RDT_CTRL_DATA			1
#define 	RDT_CTRL_ACK			2
#define		RDT_CTRL_END			3



typedef struct _STATE_PKT
{
	struct timeval send_time;
	int pkt_seq;
	int pkt_len;
	int state; //init 0 , sent 1 , acked 2 , timeout 3 , empty 4
	char rdt_pkt[RDT_PKT_LEN];
}STATE_PKT;

typedef struct _SLD_WIN
{
	//[send_left, send_right) sequence number
	int win_len;
	int send_left;
	int send_right;
	STATE_PKT rdt_pkts[RDT_SENDWIN_LEN];
	pthread_mutex_t lock;
}SLD_WIN;


int pack_rdt_pkt( char *data_buf, char *rdt_pkt, int data_len, int seq_num, int flag );
int unpack_rdt_pkt( char *data_buf, char *rdt_pkt, int pkt_len, int *seq_num, int *flag );
void udt_sendto( int sock_fd, char *pkt, int pkt_len, int flags, struct sockaddr *recv_addr, int addr_len );
int time_out( struct timeval time_1, struct timeval time_2 );


#endif

linux下在文件夹下打开端口运行make命令,会生成四个可执行文件在build子目录下: tcp_receiver,tcp_sender,udp_receiver,udp_sender
然后在子目录下打开两个端口分别运行receiver和sender,可以实现两机聊天功能。
如果make命令不存在,可以尝试使用gcc。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值