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。