概述
socket() --得到文件描述符!
bind() --我们在哪个端口?
listen() --有人给我打电话吗?
accept() --"Thank you for calling port 3490."
connect() --Hello!
send() 和 recv() --Talk to me, baby!
select()
1.socket函数
功能:指定协议类型
定义:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int family, int type, int protocol);
返回值
出错: -1
成功: 套接口描述字 (socket file descriptor)(套接字)sockfd
socket 函数指定了协议族(IPv4、IPv6或unix)和套接口类型(字节流、数据报或原始套接口)。但并没有指定本地协议地址或远程协议地址。
理解socket
socket使用 Unix 文件描述符 (file descriptor) 和其他程序通讯的方式。
Unix 程序在执行任何形式的 I/O 的时候,程序是在读或者写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数。这个文件可能是一个网络连接,FIFO,管道,终端,磁盘上的文件或者什么其他的东西。Unix 中所有的东西是文件!因此,与 Internet 上别的程序通讯的时候,要通过文件描述符。
利用系统调用 socket()得到网络通讯的文件描述符。他返回套接口描述符 (socket descriptor),然后再通过他来调用 send() 和 recv()。
那么为什么不用一般的调用 read() 和 write() 来通过套接口通讯?
简单的答案是:可以使用一般的函数!
详细的答案是:使用 send() 和 recv() 让你更好的控制数据传输。
2.bind 函数
功能:给套接口分配一个本地协议地址
定义:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *my_addr, int addrlen);
sockfd 是调用 socket 返回的文件描述符。
my_addr 是指向数据结构 struct sockaddr 的指针,保存地址(即端口和 IP 地址) 信息。
addrlen 设置为 sizeof(struct sockaddr)。
返回: 0—成功, -1---出错
my_addr.sin_port = 0; /* choose an unused port at random */
my_addr.sin_addr.s_addr = INADDR_ANY; /* use my IP address */
bind( ) 自己选择合适的端口:将0赋给 my_addr.sin_por。
自动填上他所运行的机器的 IP 地址:my_addr.sin_addr.s_addr 设置为 INADDR_ANY。
3.listen 函数
功能:将未连接主动套接口的转换为被动套接口,指示内核接受对该套接口的连接请求.
CLOSED --? LISTEN
定义:
#include <sys/socket.h>
int listen(int sockfd, int backlog);
sockfd 是调用 socket() 返回的套接口文件描述符。
backlog 是在进入队列中允许的连接数目。
监听套接口的两个队列
未完成连接队列(incompleted connection queue): SYN_RECV
已完成连接队列(completed connection queue): ESTABLISHED
当一个客户的SYN到达时,如两队列都满的, TCP将忽略该分节且不发RST
4.ACCEPT 函数
功能:在已完成队列头返回下一个已完成的连接
定义
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *cliaddr, int* addrlen);
调用成功时返回:
1. cliaddr: 客户进程的协议地址和地址大小 2. 新套接口描述字(已连接套接口描述字)
监听套接口描述字 listening socket descriptor
一个给定的服务器常常是只生成一个监听套接口, 且一直存在,直到该服务器关闭。
已连接套接口描述字connected socket descriptor
内核为每个被接受的客户连接创建了一个已连接套接口。当服务器完成某客户的服务时,关闭已连接套接口。
1024以下的端口:超级用户使用
功能:建立与TCP服务器的连接
头文件: #include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
serv_addr 是保存着目的地端口和 IP 地址的数据结构 struct sockaddr
//addrlen 设置为 sizeof(struct sockaddr)
connect 激发 TCP的三路握手过程,服务器必须准备好接受外来的连接。这通过调用socket,bind和1isten函数来完成,称为被动打开(Passive open);客户通过调用connect进行主动打开(active opn)。这引起客户TCP发送一个SYN分节(表示同步),它告诉服务器客户将在(待建立的)连接中发送的数据的初始序列号。服务器必须确认客户的SYN,同时自己也得发送一个SYN分节,它含有服务器将在同一连接中发送的数据的韧始序列号。服务器以单个分节向客户发送SYN和对客户SYN的ACK。客户必须确认服务器的SYN。
connect 出错时的返回
出错原因 :未收到SYN的响应(服务器超时,75s)
返回值:ETIMEDOUT
用户端输出:Connection time out.
出错原因 :收到RST响应(Hard error)SYN到达服务器,但该服务器的无此项端口服务
返回值:ECONNREFUSE
用户端输出:Connection refused
出错原因 :ICMP错误:不可路由(soft error)(目的地不可达)
返回值:EHOSTUNREACH
用户端输出:ENETUNREACH No route to host
6.fork 函数
功能:派生新进程 create new process
定义:
#include <sys/unistd.h>
pid_t fork (void);
在子进程中返回0,在父进程中返回子进程的进程ID
出错时返回 –1,调用一次返回两次
fork的典型应用:
1.一个进程可为自己创建一个拷贝。当一个拷贝处理一个操作时,其他的拷贝可以执行其他的任务。这是非常典型的网络服务器。
2.一个进程想执行其他的程序,由于创建新进程的唯一方法是调用fork,进程首先调 用fork来生成一个拷贝,然后其中一个拷贝(通常为子进程)调用exec 来代替自己去执行新程序。
7.select函数
select()的机制中提供一fd_set的数据结构,实际上是一long类型的数组, 每一个数组元素都能与一打开的文件句柄(不管是Socket句柄,还是其他 文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成, 当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执 行了select()的进程哪一Socket或文件可读,下面具体解释:
#include <sys/select.h>
#include <sys/time.h>
int select( int maxfd,f d_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout )
该函数允许进程指示内核等待多个事件的任何一个发生,并且仅在有一个或多个事件发生或等待一段指定的时间后才唤醒。其中,描述字不局限于套接口,任何描述字都可以使用select来测试。
参数描述:
maxfd:select监视的文件句柄数,视进程中打开的文件数而定,一般设为呢要监视各文件 中的最大文件号加一。
readset:select监视的可读文件句柄集合。
writeset:select监视的可写文件句柄集合。
exceptset:select监视的异常文件句柄集合。
timeout:本次select()等待超时结束时间,可精确至百万分之一秒。
返回值:
正整数 (就绪描述字的数目);0 (超时);-1 (出错)
注意事项:
1.select一般用于服务器程序中(listen函数之后accept函数之前);
2.select一般位于for循环中,循环侦听;
3.每次select之前都要FD_ZERO;FD_SET();以及还原超时时间变量
while(1){
FD_ZERO(&readset);
FD_SET(server_s,&readset);
timeout .tv_sec = 30;
timeout .tv_usec = 0;
ret = select(maxfdpl+1,&rset,NULL,0,&timeout );
if(ret==0){
printf("Time out. Exited!/n");
return 0;
}
if(FD_ISSET(server_s,&readset)){
accept();
read();
write();
......
}
}
基于TCP套接口的服务器客户端例子
以下是我写的一个例子,在redhat9.0上可以正常运行
1. 头文件:defines.h
服务器和客户端程序都要用到
--------------------------------------------------------------------
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <net/if.h>
#define debug
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
typedef struct s_to_c_packet{
char code[16];
struct hostinfo
{
char hostname[32];
char mac[6];
char ip[4];
} hostinfo;
} srvtoclt;
typedef struct c_to_s_packet
{
char code[16];
} clttosrv;
--------------------------------------------------------------
2. 服务器程序:hostinfo.c
--------------------------------------------------------------
/*********************************************
function: hostinfor server . provide local hostname,
IP ,MAC,etc, for client
auther: chenliang
date: 2007-10-24
*********************************************/
#include "defines.h"
int main(void){
int server_s;
int fd;
int connfd;
int ip;
int recv_count;
unsigned int server_port;
struct sockaddr_in server_sockaddr;
char hostname[32];
struct hostent *hptr = NULL;
char **pptr;
char str[32];
char msg[128];
struct ifreq req;
struct sockaddr_in *sin;
struct sockaddr_in from;
unsigned int fromlen;
unsigned int slength;
struct sockaddr *phwaddr;
srvtoclt ackpacket;
clttosrv *check;
#ifndef FD_SETSIZE
#define FD_SETSIZE 256
#endif
fd_set rset;
int maxfdpl;
int ret;
int optVal;
struct timeval timer;
FD_ZERO(&rset);
timer.tv_sec = 30;
timer.tv_usec = 0;
optVal = 1;
memset(hostname, 0, 64);
memset(&server_sockaddr, 0, sizeof server_sockaddr);
server_port = 3721;
fromlen = (socklen_t) sizeof(from);
if( gethostname(hostname, 64)<0 ){
printf("gethostname error/n");
return -1;
}
strncpy( req.ifr_name, "eth0", 16 );
sin = (struct sockaddr_in *)&req.ifr_addr;
fd = socket( PF_INET, SOCK_RAW, IPPROTO_ICMP );
if( fd < 0 ){
printf("socket fd error/n");
}
if( ioctl( fd, SIOCGIFADDR, &req) == 0 )
{
ip = sin->sin_addr.s_addr;
}else{
perror("ioctl SIOCGIFADDR error: ");
return -1;
}
phwaddr = &req.ifr_hwaddr;
if( ioctl( fd, SIOCGIFHWADDR, &req) == 0 )
;
else
perror("ioctl SIOCGIFHWADDR error: ");
#ifdef debug
printf("hostname:%s/n", hostname);
printf( "IP address:%s/n", inet_ntoa(ip) );
printf( "MAC address:%02x:%02x:%02x:%02x:%02x:%02x/n",phwaddr->sa_data[0],phwaddr->sa_data[1], phwaddr->sa_data[2], phwaddr->sa_data[3], phwaddr->sa_data[4], phwaddr->sa_data[5] );
#endif
close(fd);
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_port = htons(server_port);
server_sockaddr.sin_addr.s_addr = INADDR_ANY;
server_s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (server_s == -1) {
printf("unable to create socket/n");
}
setsockopt(server_s, SOL_SOCKET, SO_REUSEADDR, &optVal, sizeof(optVal));
//fcntl(server_s, F_GETFL, O_NONBLOCK);
if( bind(server_s,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))!=0 )
{
printf("server create socket error:%d/n",server_s);
exit(-1);
};
if( listen(server_s, 64)!=0 ){
printf("server create socket error:%d/n",server_s);
exit(-1);
}
maxfdpl = server_s;
FD_SET(server_s,&rset);
while(1){
FD_ZERO(&rset);
FD_SET(server_s,&rset);
timer.tv_sec = 30;
timer.tv_usec = 0;
memset(msg,0,sizeof(msg));
printf("timer:%dsec/n",timer.tv_sec);
ret = select(maxfdpl+1,&rset,NULL,0,&timer);
if(ret==0){
printf("Time out. Exited!/n");
return 0;
}
if(FD_ISSET(server_s,&rset)){
connfd = accept(server_s, (struct sockaddr *) &from, &fromlen);
ip = from.sin_addr.s_addr;
recv_count = read( connfd,(void *)msg,sizeof(msg) );
check = (clttosrv *)msg;
if((recv_count<8) ||strncmp(check->code,"get server info",15))
{
printf("server packet code error/n");
printf("check->code: %s/n",check->code);
return -1;
}
strcpy( ackpacket.code, "server info" );
strcpy( ackpacket.hostinfo.hostname, hostname );
memcpy( ackpacket.hostinfo.mac, phwaddr->sa_data,6);
memcpy( ackpacket.hostinfo.ip, &ip,4);
slength = write( connfd,(void *)&ackpacket, sizeof(ackpacket) );
if((slength<0) || (slength !=sizeof(ackpacket))){
printf( "server send error!%d,%d/n",slength,sizeof(ackpacket));
exit(-1);
}
printf("Host %s connect in, server info transfered successfully!/n",inet_ntoa(ip));
close(connfd);
}
}
return 0;
}
-----------------------------------------------------------------------------------
3.客户端程序:getinfo.c
-----------------------------------------------------------------------------------
#include "defines.h"
int main(void){
int socket_c;
int ip;
int ret;
int server_port;
struct sockaddr_in servaddr;
clttosrv msg;
unsigned int fromlen;
unsigned int len;
char hostinfo[128];
srvtoclt * scpacket;
char *pchar;
server_port = 3721;
strcpy(msg.code,"get server info");
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("172.16.3.25");
servaddr.sin_port = htons(server_port);
#ifdef debug
ip = servaddr.sin_addr.s_addr;
printf( "Server IP address:%s/n", inet_ntoa(ip) );
#endif
socket_c = socket(AF_INET, SOCK_STREAM,IPPROTO_TCP);
if(socket_c<0){
printf("client create socket error:%d/n", socket_c);
return -1;
}
ret = connect( socket_c, (struct sockaddr *)&servaddr, sizeof(servaddr) );
if(ret<0){
printf("connect error:%d/n");
return -1;
}
len = write(socket_c,(void *)&msg,sizeof(msg));
if((len<0) || (len !=sizeof(msg))){
printf( "server send error!%d,%d/n",len,sizeof(len));
exit(-1);
}
fromlen = read( socket_c,(void *)hostinfo,sizeof(hostinfo) );
scpacket = (srvtoclt *)hostinfo;
if(strncmp(scpacket->code,"server info",11)!=0){
printf("Discard other info/n");
exit(-1);
}
memcpy(&ip,scpacket->hostinfo.ip,4);
pchar = (char *)&scpacket->hostinfo.mac;
printf("server info recieved:/n");
printf("Host name:%s/n",scpacket->hostinfo.hostname);
printf("IP address:%s/n",inet_ntoa(ip));
printf("MAC address:%02x:%02x:%02x:%02x:%02x:%02x/n",pchar[0],pchar[1],pchar[2],pchar[3],pchar[4],pchar[5]);
close(socket_c);
return 0;
}