最近重学了网络编程,顺便修改了国嵌的网络编程视频一节的tcp服务器程序,颇有心得。
先介绍函数以供以后使用。
函数1、int socket(int domain, int type, int protocol);(以后的每个函数后面的英文都是摘自posix标准)
The socket() function shall create an unbound socket in a communications domain, and return a file descriptor that can be used in later function calls that operate on sockets.
其中第一个形参是协议族,如AF_INET(IP协议族),第二个形参是socket的类别,其中有三种:SOCK_STREAM(用于TCP)、SOCK_DGRAM(用于UDP)、SOCK_RAW(主要用于新网络协议的测试),返回值是socket描述符。
例子:int sockfd=socket(AF_INET,SOCK_STREAM,0);
函数2、int bind(int socket, const struct sockaddr *address,socklen_t address_len);
The bind() function shall assign a local socket address address to a socket identified by descriptor socket that has no local socket address assigned. Sockets created with the socket() function are initially unnamed; they are identified only by their address family.
第一个形参是你刚才创建的套接字描述符,(这个是用在服务器上的函数),第二个形参是结构体struct sockaddr,注意的是我们经常使用sockaddr_in结构体,所以在这里使用时必须强制类型转换。第三个形参是对结构体求大小,用sizeof。这里我说一个困惑:不知道为什么,如果在函数的开头定义部分,将一个指针指向sockaddr_in结构体即struct sockaddr_in *server_sock,会出现段错误的提示,执行不了,不知为什么??
例子:
int bind_ret=bind(sockfd,(struct sockaddr *)(&server_sock),sizeof(struct sockaddr_in));
函数3、int listen(int socket, int backlog);
The listen() function shall mark a connection-mode socket, specified by the socket argument, as accepting connections.
第一个形参一样,不讲。第二个是指定你的服务器能处理的最大连接数目。
例子: listen_ret=listen(sockfd,MAX_CONNUM);
函数4、int accept(int socket, struct sockaddr *restrict address,socklen_t *restrict address_len);
The accept() function shall extract the first connection on the queue of pending connections, create a new socket with the same socket type protocol and address family as the specified socket, and allocate a new file descriptor for that socket.
第一个形参是你服务器创建的套接字描述符(这个函数也是用在服务器上),第二个形参是你的客户端的sockaddr(sockaddr_in)结构体,第三个形参是该结构体的大小。
例子:
accept_sockfd=accept(sockfd,(struct sockaddr *)(&client_sock),&size))
函数5、int connect(int socket, const struct sockaddr *address,socklen_t address_len);
The connect() function shall attempt to make a connection on a socket.
第一个形参是你客户端创建的套接字描述符,第二个形参是服务器端的sockaddr结构体,之后第三个是该结构体长度。这个函数用在客户端上,注意了,在客户端上我们需要填写的服务器端的sockaddr结构体,这样与服务器端相匹配到时才能按照网咯地址找到服务器端。
至于读写我们可以用read、write或是recv、send函数,这里就不介绍了。
例子:
connect_ret=connect(sockfd,(struct sockaddr *)(&server_sock),sizeof(struct sockaddr_in);
函数6、htons与htonl、ntons、ntonl。原型分别是:
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
these functions shall convert 16-bit and 32-bit quantities between network byte order and host byte order.On some implementations, these functions are defined as macros.The uint32_t and uint16_t types are defined in <inttypes.h>.
形参是你想转换的值。
函数7、地址转换函数:
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
char *inet_ntoa(struct in_addr in);
The inet_addr() function shall convert the string pointed to by cp, in the standard IPv4 dotted decimal notation, to an integer value suitable for use as an Internet address.
The inet_ntoa() function shall convert the Internet host address specified by in to a string in the Internet standard dot notation.
例子:
函数8、获取主机信息的结构体函数:
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, socklen_t len,
int type);
These functions shall retrieve information about hosts. This information is considered to be stored in a database that can be accessed sequentially or randomly. Implementation of this database is unspecified.
例子:structhosten *host; host=gethostbyname(argv[1]);
以上就是网络编程需要常用的函数,这是应用层的。以下是我的改编函数,还有很多不足,原理编写一个简单的并发服务器,可以同时处理多个客户端的要求,并且在客户端中,如果你什么都不输入而直接按ENTER键的话会提示你是否真要退出,若否则按ENTER键确认默认选择,否则按y退出,在服务器端也会判断客户端是否真要退出,判断不成立的话继续接受客户端的信息,可能写的不是很好,但愿看得懂!:
服务器函数:
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <strings.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define portnumber 12345
#define MAX_BUF 1024
#define MAX_CONNUM 10
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in server_sock;
struct sockaddr_in client_sock;
char buff[MAX_BUF];
int nbytes;
int listen_ret;
int client_num=0;
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"create a server socket fail\n");
exit(1);
}
bzero(&server_sock,sizeof(&server_sock));
server_sock.sin_family=AF_INET;
server_sock.sin_port=htons(portnumber);
server_sock.sin_addr.s_addr=htonl(INADDR_ANY);
//server_sock.sin_addr.s_addr=inet_addr("192.168.0.117");
int bind_ret=bind(sockfd,(struct sockaddr *)(&server_sock),sizeof(struct sockaddr_in));
if (bind_ret==-1)
{
perror("bind failed\n");
exit(1);
}
if( listen_ret=listen(sockfd,MAX_CONNUM)<0)
{
perror("listen failed\n");
exit(1);
}
while(1)
{
int accept_sockfd;
int size=sizeof(struct sockaddr_in);
if((accept_sockfd=accept(sockfd,(struct sockaddr *)(&client_sock),&size))<0)
{
fprintf(stderr,"server accept error!!\n");
exit(1);
}
client_num++;
printf("server get connection from %s,the num of client is %d\n",inet_ntoa(client_sock.sin_addr),client_num);
if(fork()==0)
{
do
{
nbytes=read(accept_sockfd,buff,MAX_BUF);
buff[nbytes-1]='\0';
if(buff[0]=='\0')
{
printf("this time we recieve nothing from the client!\nwe wait the client quit or not\n");
nbytes=read(accept_sockfd,buff,MAX_BUF);
buff[nbytes-1]='\0';
if(buff[0]!='\0')
printf("client %d still not quit,we can continue recieve msg\n",client_num);
}
else
printf("we recieve the string of %s from the client %d\n",buff,client_num);
}while(buff[0]!='\0');
close(accept_sockfd);
printf("so the num of client %d has quit!\n",client_num);
exit(0);
}
}
close(sockfd);
exit(0);
}
客户端:
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define portnumber 12345
int main(int argc,char *argv[])
{
int sockfd;
struct sockaddr_in server_sock;
struct hostent *host;
int nbytes;
char buf[1024];
char str;
if(argc!=2)
{
fprintf(stderr,"Usage:%s hostneme \a\n",argv[0]);
exit(1);
}
if((host=gethostbyname(argv[1]))==NULL)
{
fprintf(stderr,"get hostname error\n");
exit(1);
}
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd==-1)
{
fprintf(stderr,"create a client socket fail\n");
exit(1);
}
bzero(&server_sock,sizeof(&server_sock));
server_sock.sin_family=AF_INET;
server_sock.sin_port=htons(portnumber);
server_sock.sin_addr=*((struct in_addr*)host->h_addr);
//server_sock->sin_addr.S_addr=inet_aton("192.168.1.11",ip_addr);
int connect_ret;
char *reply="not exit";
if(connect_ret=connect(sockfd,(struct sockaddr *)(&server_sock),sizeof(struct sockaddr_in))<0)
{
fprintf(stderr,"client get connection failed\n");
exit(1);
}
while(1)
{
printf("please input something\n");
fgets(buf,1024,stdin);
nbytes=write(sockfd,buf,strlen(buf));
if (nbytes<0)
{
printf("send failed\n");
exit(1);
}
buf[strlen(buf)-1]='\0';
if(strlen(buf)==0)
{
printf("You input nothing and press ENTER key,so we consider you want to quit the client!\n");
printf("Are you sure to quit the client?the default choice is not,if you do not want to exit,you can press ENTER key!\n");
str=getchar();
if(str=='y')
break;
else
if(str=='\n')
{
printf("you still not want to quit the client!\n");
write(sockfd,reply,8);
}
}
else
printf("we send %d octets from the client,string is %s\n",strlen(buf),buf);
}
printf("the client has quit!you need not input anything!\n");
close(sockfd);
exit(0);
}
接下来介绍几个上周用过的函数:
函数9、绝对值函数:int abs(int a);long int labs(long int b);
函数10、求商与余数函数:div_t div(int numerator,int denominator);ldiv_t div(long int numerator,long int denominator);
div_t是一个结构体,包含了商与余数两个字段。第一个形参是分子,第二个是分母。
函数11、随机数函数:int rand(void);void srand(unsigend int seed);
函数12、字符串转换函数:int atoi(char const *string);------将合法的字符转换为指定的值。
long int atol(char const *string);
以上两个函数均是以10为基数,待会在介绍怎么转换的。
long int strtol(char const *string ,char **unused,int base),
unsigned long int strtoul(char const *string ,char ** unused ,int base)
这两个函数功能比前面两个强一点,可以指出转换到哪结束,并将指针指向那里,而且基数可以自己指定。这里附上一个例子程序。
怎么转换的呢?举个例子:用第一个函数,输入“13ab”,输出是13,ab对于基数10来讲是非法数值。而采用第三个函数时,基数我们假定为15,则输出的是4211,这样算的((1*15+3)*15+10)*15+11,查看源码可以知道为什么?
还有就是第二个形参,函数希望你传递给它的是一个指针变量的地址。我们可以这么想,第一个函数的形参是char * string ,所以传递给的是一个指针(地址),所以这样调用:char *string;atoi(string),string的值是存放在该地址(具体是哪个编译器决定)的值,即是它指向的变量的地址。以此类推,第三个函数的第二个形参需要的是指针变量的地址,既然这样,我们就需要的是string这个名字的内存地址所在,因为string是指针变量,地址那就是&string这个值了!!!!!!!懂不???YES!以后遇到这种形参就会填写了!记住了!建议不懂可以画个调用图。以下是例子程序,里面便有如上所说的情况:
#include <stdlib.h>
#include <stdio.h>
void third_ptr(char ***string);
void fourth_ptr(char ****string);
void main(void)
{
int atoi_ret;
long int atol_ret,strtol_ret;
char *ptr=NULL;
unsigned long strtoul_ret;
const char *atoi_str="89";
atoi_ret=atoi(atoi_str);
printf("atoi_ret=%d\n",atoi_ret);
const char *atol_str="901";
atol_ret=atol(atol_str);
printf("atol_ret=%d\n",atol_ret);
const char *strtol_str=" aft?pl";
strtol_ret=strtol(strtol_str,&ptr,16);
printf("strtol_ret=%d,unused=%s\n",strtol_ret,ptr);
//printf("unused=%d\n",unused);
const char *strtoul_str="ab::i";
strtoul_ret=strtoul(strtoul_str,NULL,20);
printf("strtoul_ret=%d\n",strtoul_ret);
char *str=NULL;
char **str1;
str1=&str;
third_ptr(&str1);
printf("str=%s\n",str);
char *str2=NULL;
char **str3;
str3=&str2;
char ***str4;
str4=&str3;
fourth_ptr(&str4);
printf("str4=%s\n",str2);//this str2 is the first ptr in this relation!we get this complex
// relationship step by step
}
void third_ptr(char ***string)
{
char *str="according to this test program,we can understand the multiptr well!";
**string=str;
}
void fourth_ptr(char ****string)
{
char *str="test again!";
***string=str;
}
函数13、断言函数:assert(int expression),很有用的!!再次不再说了。
到此结束,还有一些不常用的不说了。