Socket编程
连接:
服务端调用socket(),bind(),listen()完成初始化以后,调用accept()阻塞等待。处于监听端口状态。客户端调用socket函数初始化后调用connect()发出SYN并阻塞等待服务器应答,服务器回答一个SYN-ACK段,客户端收到后从connect()返回。同时应答一个ACK。服务器收到后从accept()返回。
例子
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位端口号和32位IP地址,IPv6地址用sockaddr_in6结构体表示,包括16位端口号、128位IP地址和一些控制字段。
structsocketaddr_in
{
unsignedshort int sin_family;
uint16_tsin_port;
structin_addr sin_addr;
unsignedchar sin_zero[8];
};
sin_family:指定通信的地址类型。如果是TCP/IP通信,则该值为AF_INET。
sin_port:套接字使用的端口号。
sin_addr:需要访问的IP地址。in_addr也是一个结构体,定义方法如下所示。 作用是保存一个IP地址。
structin_addr
{
uint32_ts_addr;
};
sin_zero:未使用的字段,填充为0。
例如下面程序中
servaddr.sin_family= AF_INET;
servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
#defineMAXLINE 80
#defineSERV_PORT 8000
intmain()
{
structsockaddr_in servaddr,cliaddr;
socklen_tcliaddr_len;
intlistenfd,connfd;
charbuf[MAXLINE];
charstr[INET_ADDRSTRLEN];
inti,n;
listenfd= socket(AF_INET,SOCK_STREAM,0);
intsocket(int family, int type, int protocol);
socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符,应用程
序可以像读写文件一样用read/write在网络上收发数据,如果socket()调用出错则返回-1。对于IPv4,family参数指定为AF_INET。对于TCP协议,type参数指定为SOCK_STREAM,表示
面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的
传输协议。protocol参数指定为0即可。
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family= AF_INET;
servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表
示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设
置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地
址,端口号为SERV_PORT,我们定义为8000。
htonl,htons, ntohl, ntohs - convert values between host and network byteorder
bind(listenfd,(structsockaddr *)&servaddr,sizeof(servaddr));
intbind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
sockaddr_in的功能与 soockaddr相同,也是用来保存一个套接字的信息。不同的是将
IP地址与端口分开为不同的成员
bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
listen(listenfd,20);
intlisten(int sockfd, int backlog);
listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接待状态,如果接收到更多的连接请求就忽略。listen()成功返回0,失败返回-1。
printf("acceptingconnect.....\n");
while(1) {
cliaddr_len= sizeof(cliaddr);
connfd= accept (listenfd,(struct sockaddr*)&cliaddr,&cliaddr_len);
intaccept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
三方握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连
接请求,就阻塞等待直到有客户端连接上来。cliaddr是一个传出参数,accept()返回时传出客户
端的地址和端口号。addrlen参数是一个传入传出参数(value-resultargument),传入的是调
用者提供的缓冲区cliaddr的长度以避免缓冲区溢出问题,传出的是客户端地址结构体的实际长
度(有可能没有占满调用者提供的缓冲区)。如果给cliaddr参数传NULL,表示不关心客户端的
地址。
n= read(connfd,buf,MAXLINE);
printf("receviedfrom %s at port\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
inet_ntop- convert IPv4 and IPv6 addresses from binary to text form
constchar *inet_ntop(int af, const void *src, char *dst, socklen_t size);
for( i = 0; i< n ;i++) {
buf[i]= toupper(buf[i]);
}
write(connfd,buf, n);
close(connfd);
}
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#defineMAXLINE 80
#defineSERV_PORT 8000
intmain(int argc ,char * argv[])
{
structsockaddr_in servaddr;
charbuf[MAXLINE];
intsockfd,n;
char*str;
if(argc != 2) {
fputs("usage:./client message\n", stderr);
exit(1);
}
str= argv[1];
sockfd= socket(AF_INET,SOCK_STREAM,0);
servaddr.sin_family= AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port= htons(SERV_PORT);
connect(sockfd,(structsockaddr *)&servaddr,sizeof(servaddr));
intconnect(int sockfd, const struct sockaddr *servaddr, socklen_t
addrlen);
由于客户端不需要固定的端口号,因此不必调用bind(),客户端的端口号由内核自动分配。
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,区别在于bind的参数是
自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
write(sockfd,str,strlen(str));
n= read(sockfd,buf,MAXLINE);
printf("Responsefrom server:\n");
// write(STDOUT_FILENO,buf, n);
if(n!=write(STDOUT_FILENO,buf,n))//把buf写到标准输出中
perror("writeerror");
close(sockfd);
return0;
}
参考linux一站式学习
socket_wrap.h
externvoid perr_exit(const char *s);
externint Accept(int fd,struct sockaddr *sa,socklen_t *salenptr);
externvoid Bind( int fd,const struct sockaddr * sa,socklen_t salen);
externvoid Connect(int fd,const struct sockaddr *sa,socklen_t salen);
externvoid Listen (int fd,int backflag);
externint Socket (int family,int type,int protocol);
externsize_t Read (int fd,void * ptr,size_t nbytes);
externsize_t Write (int fd,const *ptr,size_t nbytes);
externvoid Close (int fd);
/*
TCP协议是面向流的,read和write调用的返回值往往小于参数指定的字节数。对于read调用,
如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用,如果请求
写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节
全部交给发送缓冲区才返回,但如果socket文件描述符有O_NONBLOCK标志,则write不阻
塞,直接返回20。
确保读写我们所请求的字节数
*/
externsize_t Readn(int fd, char *vptr,size_t n);
externssize_t Writen(int fd, const void *vptr, size_t n);
socket_wrap.c
#include<stdlib.h>
#include<errno.h>
#include<sys/socket.h>
voidperr_exit(const char *s)
{
perror(s);
exit(1);
}
intAccept(int fd,struct sockaddr *sa,socklen_t *salenptr)
{
intn;
intflag=1;
while(flag) {
if( (n = accept(fd,sa,salenptr))<0) {
if((errno == ECONNABORTED) || (errno == EINTR))
continue;
else
{
flag= 0;
perr_exit("accepterror");
}
}else {
flag= 0;
}
}
returnn;
}
voidBind( int fd,const struct sockaddr * sa,socklen_t salen)
{
if( bind(fd,sa,salen) <0)
perr_exit("binderror");
}
voidConnect(int fd,const struct sockaddr *sa,socklen_t salen)
{
if(connect(fd,sa,salen)<0)
perr_exit("connecterror");
}
voidListen (int fd,int backflag)
{
if(listen(fd,backflag)<0)
perr_exit("listenerror");
}
intSocket (int family,int type,int protocol)
{
int n;
if( (n = socket(family,type,protocol))<0)
perr_exit("socketerror");
returnn;
}
size_tRead (int fd,void * ptr,size_t nbytes)
{
size_tn;
intflag=1;
while(flag) {
if( (n = read(fd,ptr,nbytes))==-1) {
if( errno == EINTR )
continue;
else
flag=0;
}
else
flag= 0;
}
returnn;
}
size_tWrite (int fd,const *ptr,size_t nbytes)
{
size_tn;
intflag=1;
while(flag) {
if((n = write(fd,ptr,nbytes))==-1) {
if(errno == EINTR)
continue;
else
flag=0;
}
else
flag=0;
}
returnn;
}
voidClose (int fd)
{
if(close(fd)==-1)
perr_exit("closeerror");
}
/*
TCP协议是面向流的,read和write调用的返回值往往小于参数指定的字节数。对于read调用,
如果接收缓冲区中有20字节,请求读100个字节,就会返回20。对于write调用,如果请求
写100个字节,而发送缓冲区中只有20个字节的空闲位置,那么write会阻塞,直到把100个字节
全部交给发送缓冲区才返回,但如果socket文件描述符有O_NONBLOCK标志,则write不阻
塞,直接返回20。
确保读写我们所请求的字节数
*/
size_tReadn(int fd, char *vptr,size_t n)
{
size_tnleft;
size_tnread;
char*ptr;
ptr= vptr;
nleft= n;
while(nleft >0) {
if ( (nread = read(fd,ptr,nleft))<0 ) {
if(errno ==EINTR)
nread = 0;
else
return -1;
}else if (nread == 0)
break;
nleft -= nread;
ptr += nread;
}
returnn - nleft;
}
size_tWriten(int fd, const void *vptr, size_t n)
{
size_tnleft;
size_tnwritten;
constchar *ptr;
ptr= vptr;
nleft= n;
while(nleft > 0) {
if( (nwritten = write(fd, ptr, nleft)) <= 0) {
if(nwritten < 0 && errno == EINTR)
nwritten= 0;
elsereturn -1;
}
nleft-= nwritten;
ptr+= nwritten;
}
returnn;
}
server.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include"socket_wrap.h"
#defineMAXLINE 80
#defineSERV_PORT 8010
intmain()
{
structsockaddr_in servaddr,cliaddr;
socklen_tcliaddr_len;
intlistenfd,connfd;
charbuf[MAXLINE];
charstr[INET_ADDRSTRLEN];
inti,n;
listenfd= Socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family= AF_INET;
servaddr.sin_addr.s_addr= htonl(INADDR_ANY);
servaddr.sin_port=htons(SERV_PORT);
Bind(listenfd,(structsockaddr *)&servaddr,sizeof(servaddr));
Listen(listenfd,20);
printf("acceptingconnect.....\n");
while(1) {
cliaddr_len= sizeof(cliaddr);
connfd= Accept (listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len);
while(1) {
n= Read(connfd,buf,MAXLINE);
if( n ==0) {
printf("theother side has beenclosed.\n"); break;
}
printf("receviedfrom %s at port %d\n",inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),ntohs(cliaddr.sin_port));
for( i = 0; i< n ;i++) {
buf[i]= toupper(buf[i]);
}
Write(connfd,buf, n);
puts(buf);
}
close(connfd);
}
}
client.c
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include"socket_wrap.h"
#defineMAXLINE 80
#defineSERV_PORT 8010
intmain(int argc ,char * argv[])
{
structsockaddr_in servaddr;
charbuf[MAXLINE];
intsockfd,n;
char*str;
if(argc != 2) {
fputs("usage:./client message\n", stderr);
exit(1);
}
str= argv[1];
sockfd= Socket(AF_INET,SOCK_STREAM,0);
servaddr.sin_family= AF_INET;
inet_pton(AF_INET,"127.0.0.1",&servaddr.sin_addr);
servaddr.sin_port= htons(SERV_PORT);
Connect(sockfd,(structsockaddr *)&servaddr,sizeof(servaddr));
while(fgets (buf,MAXLINE,stdin)!=NULL) {
Write(sockfd,buf,strlen(buf));
n= Read(sockfd,buf,MAXLINE);
printf("Responsefrom server:\n");
if(n == 0)
printf("theother side has been closed.\n");
else
Write(STDOUT_FILENO,buf, n);
// write(STDOUT_FILENO,buf, n);
// if(n!=Write(STDOUT_FILENO,buf,n))//把buf写到标准输出中
// perror("write error");
}
close(sockfd);
return0;
}
编译:
gcc-c socket_wrap.c -o socket_wrap
gcc-c client.c -o client.o
gcc-c server.c -o server.o
gccsocket_wrap server.o -o server
gccsocket_wrap client.o -o client