socket网络编程基础

IP地址结果

在winsock中,APP通过SOCKADDR_IN结构来指定IP和Port信息,
其中的sin_zero只充当填充项,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样。
unsigned long inet_addr(const char FAR* cp)用于将一个点分IP转换成一个32位无符号长整数(按网络字节顺序)。

字节顺序

在computer中把IP & Port指定成多字节数时,是按主机字节(host-byte)顺序进行的,
但在网络上指定IP & Port,标准指定必须使用big-endian即网络字节(network-byte)顺序表示。

主机字节顺序转成网络字节顺序:

u_long WSAAPI htonl( _In_ u_long hostlong);
int WSAAPI WSAHtonl(_In_  SOCKET s,
  _In_  u_long hostlong,
  _Out_ u_long *lpnetlong
);
u_short WSAAPI htons(_In_ u_short hostshort);
int WSAAPI WSAHtons(_In_  SOCKET  s,
  _In_  u_short hostshort,
  _Out_ u_short *lpnetshort
);

网络字节顺序转主机字节顺序:

u_long WSAAPI ntohl(_In_ u_long netlong);
int WSAAPI WSANtohl(_In_  SOCKET s,
  _In_  u_long netlong,
  _Out_ u_long *lphostlong
);
u_short WSAAPI ntohs(_In_ u_short netshort);
int WSAAPI WSANtohs(_In_  SOCKET  s,
  _In_  u_short netshort,
  _Out_ u_short *lphostshort
);

IP地址和主机名

IP地址不便于记忆,通常都使用主机名,使用如下这些地址&名称解析函数,
可以将主机名(如www.somewebsite.com)解析为IP地址、服务名称(如FTP)和端口号:
getaddrinfo,
getnameinfo,
gethostbyaddr,
gethostbyname,
gethostname,
getprotobyname,
getprotobynumber,
getservbyname,
getservbyport等,
同时还有这些函数对应的异步版本,如:
WSAAsyncGetHostByAddr,
WSAAsyncGetHostByName,
WSAAsyncGetProtoByName,
WSAAsyncGetProtoByNumber,
WSAAsyncGetServByName,
WSAAsyncGetServByPort等

创建socket

有两个函数可以创建套接字:

SOCKET WSAAPI socket(
  _In_ int af,//IPv4为AF_INET
  _In_ int type,//tcp为SOCK_STREAM,udp为SOCK_DGRAM
  _In_ int protocol//tcp为IPPROTO_TCP,udp为IPPROTO_UDP
);
SOCKET WSASocket(
  _In_ int                af,
  _In_ int                type,
  _In_ int                protocol,
  _In_ LPWSAPROTOCOL_INFO lpProtocolInfo,
  _In_ GROUP              g,
  _In_ DWORD              dwFlags
);
//为控制套接字的选项和行为,提供了4个API:
setsockopt
getsockopt
ioctlsocket
WSAIoctl

绑定

int bind(
  _In_ SOCKET                s,
  _In_ const struct sockaddr *name,
  _In_ int                   namelen
);
//最常见的错误是WSAEADDRINUSE:如果使用的是tcp/ip,那么表示该IP&Port已经被占用了,或者该IP&Port处于TIME_WAIT状态
//对一个已经bind的套接字调用bind,将返回WSAEFAULT错误

监听

int listen(
  _In_ SOCKET s,
  _In_ int    backlog//队列长度,超出队列时请求将被拒绝,连接段收到WSAECONNREFUSED错误
);
//最常见的错误是WSAEINVAL,表示在调用listen之前没有调用bind
//另外listen也可能接受到WSAEADDRINUSE错误,该错误通常发生在bind调用

接受连接

接受连接的API:accept, WSAAccept, AcceptEx.

SOCKET accept(
  _In_    SOCKET          s,//bindsocket
  _Out_   struct sockaddr *addr,
  _Inout_ int             *addrlen
);
//当监听套接字为异步或者非阻塞模式,并且没有连接被接受时,最常见的错误是WSAEWOULDBLOCK

服务器端过程

//1.初始化winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//2.创建监听套接字
lstSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//3.建立SOCKADDR_IN地址结构
SOCKADDR_IN srvAddr;
srvAddr.sin_faminly = AF_INET;
srvAddr.sin_port = htons(6188);
//4.绑定
bind(lstSocket, (SOCKADDR*)&srvAddr, sizeof(srvAddr));
//5.监听客户端连接
listen(lstSocket, 5);
//6.接受新连接(通常循环接受多个连接)
clientSocket = accept(lstSocket, (SOCKADDR*)&clientAddr, &clientAddrLen);
//7.在clientSocket上收发数据
//8.使用完关闭clientSocket
closesocket(clientSocket);
//9.关闭lstSocket
closesocket(lstSocket);
//10.释放
WSACleanup();

客户端过程

1)创建socket
2)建立SOCKADDR地址结构,指定服务器IP&Port
3)调用connect, WSAConnect或者ConnectEx建立客户端和服务器的连接

int connect(
  _In_ SOCKET                s,
  _In_ const struct sockaddr *name,
  _In_ int                   namelen
);
//常见错误

客户端的完整过程如下:

//1.初始化winsock
WSAStartup(MAKEWORD(2,2), &wsaData);
//2.创建客户端套接字
s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//3.建立SOCKADDR_IN地址结构,用于连接服务器
SOCKADDR_IN srvAddr;
srvAddr.sin_faminly = AF_INET;
srvAddr.sin_port = htons(6188);
srvAddr.sin_addr.s_addr = inet_addr("136.149.3.28");
//4.用套接字创建一个到服务器的连接
connect(s, (SOCKADDR*)&srvAddr, sizeof(srvAddr));
//5.使用套接字s收发数据
//6.使用完后,关闭套接字
closesocket(s);
//7.清理
WSACleanup();

TCP状态

1)对于每个套接字而言,它的初始状态都是CLOSED.

2)若服务器套接字同本地IP&Port绑定起来,并在它上面进行监听,那么套接字的状态就是LISTEN状态.

3)若客户机初始化了一个连接,就会向服务器发送一个SYN包,同时将客户机套接字状态置为SYN_SENT.

4)服务器收到SYN包后,会发送一个SYN_ACK包响应(如果服务器一直不发送SYN_ACK包,客户机就会超时,并返回CLOSED状体),服务器的套接字状态变为SYN_RCVD;

5)客户机需要用一个ACK包对它(SYN_ACK包)进行响应,此时客户机的套接字将处于ESTABLISHED状态;这个ACK包将服务器套接字的状态变成ESTABLISHED.

6) 14…

数据传输

所有收发数据的缓冲区都属于简单的char类型(面向字节的数据),它可以包含任何原始数据,这些原始数据是二进制,还是字符型,是无关紧要的。
发送:send/WSASend
接收:recv/WSARecv
它们出错返回值都是SOCKET_ERROR,最常见的错误是WSAECONNABORTED和WSAECONNRESET,两者都和正在被关闭相关(要么由于超时被关闭,要么由于通信方正在关闭连接). 另一个常见的错误是WSAEWOULDBLOCK,一般出现在非阻塞模式或异步状态时,表示函数暂时不能完成。

一个简单的socket网络编程例子: 服务器代码: #include #include #include #include #pragma comment(lib,"ws2_32.lib") //这句话的意思是加载ws2_32.lib这个静态库 #define NETWORK_EVENT WM_USER+100 //如果你用mfc做开发,你可以点击菜单project-〉setting-〉link-〉object/library中添加这个静态库。 //如果你用c语言,你需要通过#pragma comment(命令来连接静态库 int main(int argc, char* argv[]){ HANDLE hThread = NULL; //判断是否输入了端口号 if(argc!=3){ printf("Usage: %sPortNumber\n",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[2]))==0){ printf("端口号有误!"); exit(-1); } WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ //高字节指定了次版本号,低字节指定了主版本号,两个字节加到一起,就是你想要的Winsock库的版本号了 printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET serverSocket; if((serverSocket=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = htonl(INADDR_ANY); serverAddress.sin_port = htons(port); //绑定 if(bind(serverSocket,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR){ printf("套接字绑定到端口失败!端口: %d\n",port); exit(-1); } //进入侦听状态 if(listen(serverSocket,SOMAXCONN)==SOCKET_ERROR){ printf("侦听失败!"); exit(-1); } printf("Server %d is listening......\n",port); SOCKET clientSocket[5],maxSocket;//用来和客户端通信的套接字 struct sockaddr_in clientAddress;//用来和客户端通信的套接字地址 memset(&clientAddress,0,sizeof(clientAddress)); int addrlen = sizeof(clientAddress); fd_set fd_read; int i=0; int j; char buf[4096]; char buff[4096]="exit"; while(1) { FD_ZERO(&fd_read); maxSocket=serverSocket; FD_SET(serverSocket,&fd_read); //FD_SET(clientSocket[i-1],&fd_read); for(j=0;j<i;j++) { FD_SET(clientSocket[j],&fd_read); if(maxSocket"); //gets(buff); if(select(maxSocket+1,&fd_read,NULL,NULL,NULL)>0) { if(FD_ISSET(serverSocket,&fd_read)) { if(buff=="") { if((clientSocket[i++]=accept(serverSocket,(sockaddr*)&clientAddress,&addrlen))==INVALID_SOCKET) { printf("接受客户端连接失败!"); exit(-1); } else { for(j=0;j5) { printf("超过最大客户端数"); exit(-1); } } else { int bytes; for(int k=0;k<i;k++) { if(FD_ISSET(clientSocket[k],&fd_read)) { bytes=recv(clientSocket[k],buf,sizeof(buf),0); if(bytes==-1) { //listen(serverSocket,SOMAXCONN); for (int l=k;l<i;l++) clientSocket[l]=clientSocket[l+1]; i--; } /*if(bytes==0) { //printf("fdsdf"); listen(serverSocket,SOMAXCONN); for (int l=k;l0) { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(clientAddress.sin_addr),buf); if(send(clientSocket[k],buf,bytes,0)==SOCKET_ERROR) { printf("发送数据失败!"); exit(-1); } } } } } } } //清理套接字占用的资源 WSACleanup(); return 0; } 客户端代码: #include #include #include #pragma comment(lib,"ws2_32.lib") int main(int argc, char* argv[]){ //判断是否输入了IP地址和端口号 if(argc!=4){ printf("Usage: %s IPAddress PortNumber\n",argv[1]); exit(-1); } //把字符串的IP地址转化为u_long unsigned long ip; if((ip=inet_addr(argv[2]))==INADDR_NONE){ printf("不合法的IP地址:%s",argv[1]); exit(-1); } //把端口号转化成整数 short port; if((port = atoi(argv[3]))==0){ printf("端口号有误!"); exit(-1); } printf("Connecting to %s:%d......\n",inet_ntoa(*(in_addr*)&ip),port); WSADATA wsa; //初始化套接字DLL if(WSAStartup(MAKEWORD(2,2),&wsa)!=0){ printf("套接字初始化失败!"); exit(-1); } //创建套接字 SOCKET sock,serverSocket; if((sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==INVALID_SOCKET){ printf("创建套接字失败!"); exit(-1); } struct sockaddr_in serverAddress; memset(&serverAddress,0,sizeof(sockaddr_in)); serverAddress.sin_family=AF_INET; serverAddress.sin_addr.S_un.S_addr = ip; serverAddress.sin_port = htons(port); //建立和服务器的连接 if(connect(sock,(sockaddr*)&serverAddress,sizeof(serverAddress))==SOCKET_ERROR) { printf("建立连接失败!"); exit(-1); } char buf[4096]; while(1){ printf(">"); //从控制台读取一行数据 gets(buf); if(send(sock,buf,strlen(buf),0)==SOCKET_ERROR){ printf("发送c数据失败!"); exit(-1); } int bytes; if((bytes=recv(sock,buf,sizeof(buf),0))==SOCKET_ERROR) { printf("接收c数据失败!\n"); exit(-1); } else { buf[bytes]='\0'; printf("Message from %s: %s\n",inet_ntoa(serverAddress.sin_addr),buf); } } //清理套接字占用的资源 WSACleanup(); return 0; }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值