下面是使用TCP协议的服务端
#include <stdio.h>
#include <stdlib.h>
#include <map>
#include <string>
#include <winsock2.h>
#include <direct.h>
#pragma comment(lib,"ws2_32.lib")
DWORD WINAPI chat(LPVOID p)
{
SOCKET con_sock = *(SOCKET*)p;
char data[200];
memset(data,0,200);
int a;
while(1)
{
a=recv(con_sock,data,sizeof(data),0);
if(a==0 || a==SOCKET_ERROR)
{
printf("thread exit\n");
closesocket(con_sock);
exit(0);
}
else
{
printf("%d\n",strlen(data));
printf("accept information:%s\n",data);
memset(data,0,200);
}
}
}
DWORD WINAPI send_sock(LPVOID p)
{
SOCKET con_sock = *(SOCKET*)p;
char s[200];
memset(s,0,200);
while(1)
{
fgets(s,sizeof(s),stdin);
if(s[strlen(s)-1]=='\n')
{
s[strlen(s)-1]='\0';
}
send(con_sock,s,strlen(s),0);
}
}
int main(int argc,char* argv[])
{
//初始化window 套接字即(加载套接字库,下面是使用winsock2.2的版本库)
WORD sockVersion = MAKEWORD(2,2);
WSADATA wsadata;
if(0 != WSAStartup(sockVersion,&wsadata))
{
return 0 ;
}
//创建套接字 协议族,地址家族,决定着地址类型
SOCKET listen_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(listen_sock == INVALID_SOCKET)
{
printf("listen_sock fail");
}
//bind套接字
sockaddr_in addr_in;
addr_in.sin_family=AF_INET;
addr_in.sin_port = 8888;
addr_in.sin_addr.S_un.S_addr = inet_addr("192.168.14.152");
if(SOCKET_ERROR == bind(listen_sock,(sockaddr*)&addr_in,sizeof(addr_in)))
{
printf("bind fail\n");
}
//监听
listen(listen_sock,4);
//等待客户端的请求
SOCKET com_sock;
while(1)
{
DWORD newthreadid;
sockaddr_in client_addr;
int len = sizeof(client_addr);
com_sock = accept(listen_sock,(sockaddr*)&client_addr,&len);
if(com_sock == INVALID_SOCKET)
{
printf("com_sock fail");
}
else
{
printf("connect success\n");
}
printf("ip:%s\n port:%d\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
HANDLE handle = CreateThread(NULL,0,chat,(LPVOID)&com_sock,0,&newthreadid);
if(handle == NULL)
{
printf("create thread fail");
break;
}
HANDLE handle1 = CreateThread(NULL,0,send_sock,(LPVOID)&com_sock,0,NULL);
if(handle1 == NULL)
{
printf("create thread fail");
break;
}
CloseHandle(handle);
CloseHandle(handle1);
}
closesocket(com_sock);
closesocket(listen_sock);
WSACleanup();
system("pause");
return 0;
}
客户端
#include <WinSock2.h>
#include <stdio.h>
#include <sstream>
#include <string>
#include <strstream>
#include <iostream>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#pragma pack(4)
DWORD WINAPI chat(LPVOID p)
{
SOCKET con_sock=*(SOCKET*)p;
char s[200];
memset(s,0,sizeof(s));
int a;
FILE* fp;
while(1)
{
//recvfrom也可以
a=recv(con_sock,s,sizeof(s),0);
printf("%d\n",a);
if(0==a || SOCKET_ERROR==a)
{
printf("connect interrupt\n");
closesocket(con_sock);
exit(0);
}
else
{
printf("other: %s\n",s);
fp=fopen("./record.txt","a+");
if(fp==NULL)
{
printf("save message fail\n");
}
else
{
fprintf(fp," ");
fwrite(s,strlen(s),1,fp);
fwrite("\n",1,1,fp);
}
fclose(fp);
memset(s,0,sizeof(s));
}
}
}
DWORD WINAPI chat1(LPVOID p)
{
SOCKET con_sock=*(SOCKET*)p;
printf("%d\n",con_sock);
int len;
char s[200];
memset(s,0,sizeof(s));
FILE* fp;
int pos=0;
char* s1=NULL;
while(1)
{
printf("input:\n");
fgets(s,200,stdin);
len = strlen(s);
if(strcmp(s,"seek\n")!=0)
{
if(s[len-1]=='\n')
{
s[len-1]='\0';
}
printf("len2:%d\n",strlen(s));
//sendto 也可以
if(SOCKET_ERROR == send(con_sock,s,strlen(s),0))
{
printf("send fail\n");
}
else
{
fp=fopen("./record.txt","a+");
if(fp==NULL)
{
printf("save message fail\n");
}
else
{
fwrite(s,strlen(s),1,fp);
fwrite("\n",1,1,fp);
}
fclose(fp);
}
}
else
{
fp=fopen("./record.txt","r");
if(fp==NULL)
{
printf("save message fail\n");
}
fseek(fp,0,SEEK_END);
pos = ftell(fp);
s1 = (char*)malloc(pos);
rewind(fp);
fread(s1,1,pos,fp);
s1[pos]='\0';
printf(s1);
fclose(fp);
}
}
}
int main()
{
WORD word = MAKEWORD(2,2);
WSADATA wsadata;
if(WSAStartup(word,&wsadata)!= 0 )
{
return 0 ;
}
SOCKET client_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(client_sock == INVALID_SOCKET)
{
printf("client_sock fail\n");
}
printf("%d\n",client_sock);
sockaddr_in client_addr;
client_addr.sin_addr.S_un.S_addr = inet_addr("192.168.14.152");
client_addr.sin_port = 8888;
client_addr.sin_family = AF_INET;
/*
if(SOCKET_ERROR == bind(client_sock,(sockaddr*)&client_addr,sizeof(client_addr)))
{
printf("bind fail\n");
}
*/
if(SOCKET_ERROR == connect(client_sock,(sockaddr*)&client_addr,sizeof(client_addr)))
{
printf("connect fail \n");
}
/* sockaddr_in addr_in;
addr_in.sin_family=AF_INET;
addr_in.sin_port = 8888;
addr_in.sin_addr.S_un.S_addr = inet_addr("192.168.14.152");
*/
HANDLE handle = CreateThread(NULL,0,chat,(LPVOID)&client_sock,0,NULL);
if(handle == NULL)
{
printf("createthread fail\n");
}
HANDLE handle1 = CreateThread(NULL,0,chat1,(LPVOID)&client_sock,0,NULL);
if(handle1 == NULL)
{
printf("createthread1 fail\n");
}
CloseHandle(handle);
CloseHandle(handle1);
while(1);
WSACleanup();
system("pause");
return 0;
}
函数说明
int socket(int protofamily, int type, int protocol);//返回sockfd
功能:用于创建一个socket描述符(socket descriptor),和文件描述符是一样的。
参数一:protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。不同的协议族对应的地址类型不同。
参数二:type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等
参数三:protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
该函数创建一个socket套接字,参数二决定套接字类型,套接字类型对应着传输协议,参数三设置0,那么使用套接字类型默认的传输协议,有的类型可以有多种协议,有的只有一种协议
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将地址绑定给sockfd。
参数一:套接字描述符,
参数二:绑定的地址,改地址存在结构体里
参数三:结构体大小
看看这个结构体:
struct
sockaddr
{
__SOCKADDR_COMMON (sa_);
/* Common data: address family and length. 占2个字节协议族*/
char
sa_data[14];
/* Address data. 地址+端口号*/
};
struct
sockaddr_in
{
__SOCKADDR_COMMON (sin_);
/* 协议族 */
in_port_t sin_port;
/* Port number. 端口号 */
struct
in_addr sin_addr;
/* Internet address. IP地址 */
/* Pad to size of `struct sockaddr'. 用于填充的0字节 */
unsigned
char
sin_zero[
sizeof
(
struct
sockaddr) -
__SOCKADDR_COMMON_SIZE -
sizeof
(in_port_t) -
sizeof
(
struct
in_addr)];
};
struct
in_addr
{
in_addr_t s_addr;
};
二者的占用的内存大小是一致的,因此可以互相转化,从这个意义上说,他们并无区别。
int listen(int sockfd, int backlog);
listen函数的第一个参数即为要监听的socket描述字,第二个参数表示该服务端允许同时连接的个数,是服务端处理连接队列的能力。如:backlog为5,那么表示最多同时允许5个客户端连接,当有第6个来连接时,就会出错。并不是表示能够连接客户端的数量。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
connect函数的第一个参数即为客户端的socket描述字,第二参数为服务器的socket地址,第三个参数为socket地址的长度。客户端通过调用connect函数来建立与服务端的连接。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //返回连接connect_fd
功能:等待客户端的连接请求,并返回连接套接字。accept的第一个参数为服务器的socket描述字,是服务器开始调用socket()函数生成的,称为监听socket描述字;而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字,它在该服务器的生命周期内一直存在。内核为每个由服务器进程接收的客户连接创建了一个已连接socket描述字,当服务器完成了对某个客户的服务,可以关闭其连接的socket套接字,监听套接字不关闭。
参数一:监听套接字,通过监听套接字来构建一个与客户端连接的套接字。
参数二:为客户端的地址数据
参数三:为存放客户端地址数据的结构体大小
下面是发送和接收消息的函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
flags | 说明 | recv | send |
MSG_DONTROUTE | 绕过路由表查找 | • | |
MSG_DONTWAIT | 仅本操作非阻塞 | • | • |
MSG_OOB | 发送或接收带外数据 | • | • |
MSG_PEEK | 窥看外来消息 | • | |
MSG_WAITALL | 等待所有数据 | • |
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
同步Socket的send函数的执行流程,当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲的长度(因为待发送数据是要copy到套接字s的发送缓冲区的,注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里):
1.如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;
2.如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么 send就比较s的发送缓冲区的剩余空间和len:
(i)如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完;
(ii)如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里。
3.如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
注意:send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)。只有发送0字节时才会返回0。
同步Socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;
如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;
当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;
如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
说明:当接收缓冲区为0时,收不到数据。而当发送缓冲区为0时,send返回时表示数据已经在传输了。这样做会影响性能。
最后需要
int close(int fd);
关闭套接字
附录:
sendto可以在参数中指定发送的目标地址,send需要socket已建立连接,sendto可用于无连接的socket
对于有连接的socket,两者一样,sendto最后两个参数没用.
对于recvfrom ,可同时应用于面向连接的和无连接的套接字。recv一般只用在面向连接的套接字,几乎等同于recvfrom,只要将recvfrom的第五个参数设置NULL。
这里说明一下,服务端listen(sock1);监听套接字sock1,和使用accept返回的连接套接字sock2,这两个套接字相互间没有任何影响(关闭监听套接字后,连接套接字仍然可以与客户端传递信息,关闭监听套接字后,不能在与其他客户端连接了)。如,客户端csock连接服务端后,服务端会通过监听套接字产生一个连接套接字(sock2),此时关闭监听套接字,客户端csock仍能和服务端sock2通信。