实现一个简单的echo服务器
/**
* @file server.c
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 1024
#define SERVERADDR "127.0.0.1"
#define SERVERPORT 8000
#define QUEUE 20
int init(int type,const struct sockaddr *addr,socklen_t alen,int queuelen)
{
int fd;
int err = 0;
if((fd = socket(addr->sa_family,type,0)) < 0) {
return -1;
}
if(bind(fd,addr,alen) < 0) {
goto errout;
}
if(type == SOCK_STREAM || type == SOCK_SEQPACKET) {
if(listen(fd,queuelen) < 0) {
goto errout;
}
}
return fd;
errout:
err = errno;
close(fd);
errno = err;
return (-1);
}
int main(int argc, char *argv[])
{
char ipStr[MAXLINE] = {0};
char buf[MAXLINE] = {0};
int i;
int len = 0;
int lfd,cfd;
struct sockaddr_in server_addr,client_addr;
socklen_t addrlen = sizeof(struct sockaddr_in);
bzero(&server_addr,sizeof(struct sockaddr_in));
server_addr.sin_family = AF_INET;
inet_pton(AF_INET,SERVERADDR,&server_addr.sin_addr.s_addr);
server_addr.sin_port = htons(SERVER_PORT);
init(SOCK_STREAM,(struct sockaddr *)&server_addr,addrlen,QUEUELEN);
bzero(&client_addr,sizeof(struct sockaddr_in));
printf("Accept...\n");
cfd = accept(lfd,(struct sockaddr *)&client_addr,&addrlen);
if (cfd == -1) {
perror("accept err");
exit(1);
}
printf("clientaddr ip = %s\tport = %d\n",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipStr,sizeof(ipStr)),ntohs(client_addr.sin_port));
len = read(cfd, buf, sizeof(buf));
for (i = 0; i < len; i++)
buf[i] = toupper(buf[i]);
write(cfd, buf, len);
close(cfd);
close(lfd);
return 0;
}
/**
* @file client.c
*/
#include
#include
#include
#include
#include
#define MAXSLEEP 128
#define MAXLINE 80
#define SERV_PORT 8000
//客户端的重新链接
int connect_retry(int sockfd,const struct sockaddr *addr,socklen_t alen)
{
//Linux上可行
int numsec;
//实现指数补偿,当numsec大于允许重新连接的时间的时候,就不再连接
for(numsec = 1; numsec <= MAXSLEEP;numsec <<= 1) {
if(connect(sockfd,addr,alen) == 0) {
return 0;
}
if(numsec <= MAXSLEEP/2) {
sleep(numsec);
}
}
return -1;
}
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, n;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
if(connect_retry(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) {
perror("connect error");
exit(1);
}
while (fgets(buf, MAXLINE, stdin) != NULL) {
write(sockfd, buf, strlen(buf));
n = read(sockfd, buf, MAXLINE);
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
else
write(STDOUT_FILENO, buf, n);
}
close(sockfd);
return 0;
}
#include
int socket(int family, int type, int protocol);
family:协议簇
family | 说明 |
---|---|
AF_INET | IPv4协议 |
AF_INET6 | IPv6 |
AF_LOCAL | Unix域协议 |
AF_ROUTE | 路由套接字 |
AF_KEY | 密钥套接字 |
type:套接字的类型
type | 说明 |
---|---|
SOCK_STREAM(常用) | 字节流套接字 |
SOCK_DGRAM | 数据报套接字 |
SOCK_SEQPACKET | 有序分组套接字 |
SOCK_RAW | 原始套接字 |
protocol:协议类型的常量或设置为0,以选择给定的family和type组合的系统默认值
protocol | 说明 |
---|---|
IPPROTO_TCP | TCP传输协议 |
IPPROTO_UDP | UDP传输协议 |
IPPROTO_SCTP | SCTP传输协议 |
#include
int inet_pton(int family,const char *strptr,void *addrptr);//成功返回1,格式不对返回0,出错返回-1
//作用:p代表表达式 n代表数值 以后所写的所有代码中都有可能会需要这个函数,所以这个函数很重要
//将char所指向的字符串,通过addrptr指针存放
//他的反函数: inet_ntop()作用相反。可以百度查阅这个函数的功能。因为例子里我们没有涉及到,就不介绍了。以后用到的时候再说
//需要注意的是:当他发生错误的时候,errno的值会被置为EAFNOSUPPORT 关于errno值我们一会儿介绍。
#include
int connect(int sockfd,const struct sockaddr* servaddr,socklen_t addrlen);//用connect函数来建立与TCP服务器的连接
#include
int close(int sockfd);//关闭socket,并终止TCP连接
#include
int bind(int sockfd,const struct* myaddr,socklen_t addrlen);//把本地协议地址赋予一个套接字。也就是将32位的IPv4或128位ipv6与16位的TCP或者UDP组合。
#include
int listen(int sockfd,int backlog)//成功返回0,失败返回-1 listen函数仅由TCP服务器调用
//listen函数将会做两件事:
//1:我们在创建套接字的时候使用了socket函数,它创建的套接字是主动套接字,bind函数的功能就是通过这个将主动套接字,变成被动套接字,告诉内核应该接受指向这个套接字的请//求,CLOSED状态变成LISTEN状态
//2:本函数的第二个参数规定了内核要为该套接字排队的最大连接个数。
#include
int accept(int sockfd,struct sockaddr* cliaddr,socklen_t *addrlen);//成功返回描述符,失败返回-1
//1、如果第二三个参数为空,代表了,我们对客户的身份不感兴趣,因此置为NULL;
//2、第一个参数为socket创建的监听套接字,返回的是已连接套接字,两个套接字是有区别的,而且非常重要。区别:我们所创建的监听套接字一般服务器只创建一个,并且一直存在。而内核会为每一个服务器进程的客户连接建立一个连接套接字,当服务器完成对某个给定客户的服务时,连接套接字就会被关闭。