├── AsyncSocketDemo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── AsyncSocketDemo.xccheckout └── xcuserdata │ └── ligang.xcuserdatad │ └── xcschemes │ └── AsyncSocketDemo.xcscheme ├── AsyncSocketDemo ├── AppDelegate.h ├── AppDelegate.m ├── Base.lproj │ ├── LaunchScreen.xib │ └── Main.storyboard ├── Images.xcassets │ └── AppIcon.appiconset │ │ └── Contents.json ├── Info.plist ├── LGSocketServe.h ├── LGSocketServe.m ├── ViewController.h ├── ViewController.m ├── main.m └── socket │ ├── AsyncSocket.h │ └── AsyncSocket.m ├── AsyncSocketDemoTests ├── AsyncSocketDemoTests.m └── Info.plist └── README.md /AsyncSocketDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | CE44E2101ACE7050007475F1 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E20F1ACE7050007475F1 /* main.m */; }; 11 | CE44E2131ACE7050007475F1 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E2121ACE7050007475F1 /* AppDelegate.m */; }; 12 | CE44E2161ACE7050007475F1 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E2151ACE7050007475F1 /* ViewController.m */; }; 13 | CE44E2191ACE7050007475F1 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE44E2171ACE7050007475F1 /* Main.storyboard */; }; 14 | CE44E21B1ACE7050007475F1 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE44E21A1ACE7050007475F1 /* Images.xcassets */; }; 15 | CE44E21E1ACE7050007475F1 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = CE44E21C1ACE7050007475F1 /* LaunchScreen.xib */; }; 16 | CE44E22A1ACE7050007475F1 /* AsyncSocketDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E2291ACE7050007475F1 /* AsyncSocketDemoTests.m */; }; 17 | CE44E2351ACE7081007475F1 /* LGSocketServe.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E2341ACE7081007475F1 /* LGSocketServe.m */; }; 18 | CE44E23C1ACE77EE007475F1 /* AsyncSocket.m in Sources */ = {isa = PBXBuildFile; fileRef = CE44E23B1ACE77EE007475F1 /* AsyncSocket.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | CE44E2241ACE7050007475F1 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = CE44E2021ACE704F007475F1 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = CE44E2091ACE704F007475F1; 27 | remoteInfo = AsyncSocketDemo; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | CE44E20A1ACE7050007475F1 /* AsyncSocketDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = AsyncSocketDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33 | CE44E20E1ACE7050007475F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 34 | CE44E20F1ACE7050007475F1 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 35 | CE44E2111ACE7050007475F1 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 36 | CE44E2121ACE7050007475F1 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 37 | CE44E2141ACE7050007475F1 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 38 | CE44E2151ACE7050007475F1 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 39 | CE44E2181ACE7050007475F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 40 | CE44E21A1ACE7050007475F1 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 41 | CE44E21D1ACE7050007475F1 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 42 | CE44E2231ACE7050007475F1 /* AsyncSocketDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AsyncSocketDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 43 | CE44E2281ACE7050007475F1 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 44 | CE44E2291ACE7050007475F1 /* AsyncSocketDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AsyncSocketDemoTests.m; sourceTree = ""; }; 45 | CE44E2331ACE7081007475F1 /* LGSocketServe.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LGSocketServe.h; sourceTree = ""; }; 46 | CE44E2341ACE7081007475F1 /* LGSocketServe.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = LGSocketServe.m; sourceTree = ""; }; 47 | CE44E23A1ACE77EE007475F1 /* AsyncSocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncSocket.h; sourceTree = ""; }; 48 | CE44E23B1ACE77EE007475F1 /* AsyncSocket.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AsyncSocket.m; sourceTree = ""; }; 49 | /* End PBXFileReference section */ 50 | 51 | /* Begin PBXFrameworksBuildPhase section */ 52 | CE44E2071ACE704F007475F1 /* Frameworks */ = { 53 | isa = PBXFrameworksBuildPhase; 54 | buildActionMask = 2147483647; 55 | files = ( 56 | ); 57 | runOnlyForDeploymentPostprocessing = 0; 58 | }; 59 | CE44E2201ACE7050007475F1 /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | CE44E2011ACE704F007475F1 = { 70 | isa = PBXGroup; 71 | children = ( 72 | CE44E20C1ACE7050007475F1 /* AsyncSocketDemo */, 73 | CE44E2261ACE7050007475F1 /* AsyncSocketDemoTests */, 74 | CE44E20B1ACE7050007475F1 /* Products */, 75 | ); 76 | sourceTree = ""; 77 | }; 78 | CE44E20B1ACE7050007475F1 /* Products */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | CE44E20A1ACE7050007475F1 /* AsyncSocketDemo.app */, 82 | CE44E2231ACE7050007475F1 /* AsyncSocketDemoTests.xctest */, 83 | ); 84 | name = Products; 85 | sourceTree = ""; 86 | }; 87 | CE44E20C1ACE7050007475F1 /* AsyncSocketDemo */ = { 88 | isa = PBXGroup; 89 | children = ( 90 | CE44E2391ACE77EE007475F1 /* socket */, 91 | CE44E2111ACE7050007475F1 /* AppDelegate.h */, 92 | CE44E2121ACE7050007475F1 /* AppDelegate.m */, 93 | CE44E2141ACE7050007475F1 /* ViewController.h */, 94 | CE44E2151ACE7050007475F1 /* ViewController.m */, 95 | CE44E2331ACE7081007475F1 /* LGSocketServe.h */, 96 | CE44E2341ACE7081007475F1 /* LGSocketServe.m */, 97 | CE44E2171ACE7050007475F1 /* Main.storyboard */, 98 | CE44E21A1ACE7050007475F1 /* Images.xcassets */, 99 | CE44E21C1ACE7050007475F1 /* LaunchScreen.xib */, 100 | CE44E20D1ACE7050007475F1 /* Supporting Files */, 101 | ); 102 | path = AsyncSocketDemo; 103 | sourceTree = ""; 104 | }; 105 | CE44E20D1ACE7050007475F1 /* Supporting Files */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | CE44E20E1ACE7050007475F1 /* Info.plist */, 109 | CE44E20F1ACE7050007475F1 /* main.m */, 110 | ); 111 | name = "Supporting Files"; 112 | sourceTree = ""; 113 | }; 114 | CE44E2261ACE7050007475F1 /* AsyncSocketDemoTests */ = { 115 | isa = PBXGroup; 116 | children = ( 117 | CE44E2291ACE7050007475F1 /* AsyncSocketDemoTests.m */, 118 | CE44E2271ACE7050007475F1 /* Supporting Files */, 119 | ); 120 | path = AsyncSocketDemoTests; 121 | sourceTree = ""; 122 | }; 123 | CE44E2271ACE7050007475F1 /* Supporting Files */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | CE44E2281ACE7050007475F1 /* Info.plist */, 127 | ); 128 | name = "Supporting Files"; 129 | sourceTree = ""; 130 | }; 131 | CE44E2391ACE77EE007475F1 /* socket */ = { 132 | isa = PBXGroup; 133 | children = ( 134 | CE44E23A1ACE77EE007475F1 /* AsyncSocket.h */, 135 | CE44E23B1ACE77EE007475F1 /* AsyncSocket.m */, 136 | ); 137 | path = socket; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | CE44E2091ACE704F007475F1 /* AsyncSocketDemo */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = CE44E22D1ACE7050007475F1 /* Build configuration list for PBXNativeTarget "AsyncSocketDemo" */; 146 | buildPhases = ( 147 | CE44E2061ACE704F007475F1 /* Sources */, 148 | CE44E2071ACE704F007475F1 /* Frameworks */, 149 | CE44E2081ACE704F007475F1 /* Resources */, 150 | ); 151 | buildRules = ( 152 | ); 153 | dependencies = ( 154 | ); 155 | name = AsyncSocketDemo; 156 | productName = AsyncSocketDemo; 157 | productReference = CE44E20A1ACE7050007475F1 /* AsyncSocketDemo.app */; 158 | productType = "com.apple.product-type.application"; 159 | }; 160 | CE44E2221ACE7050007475F1 /* AsyncSocketDemoTests */ = { 161 | isa = PBXNativeTarget; 162 | buildConfigurationList = CE44E2301ACE7050007475F1 /* Build configuration list for PBXNativeTarget "AsyncSocketDemoTests" */; 163 | buildPhases = ( 164 | CE44E21F1ACE7050007475F1 /* Sources */, 165 | CE44E2201ACE7050007475F1 /* Frameworks */, 166 | CE44E2211ACE7050007475F1 /* Resources */, 167 | ); 168 | buildRules = ( 169 | ); 170 | dependencies = ( 171 | CE44E2251ACE7050007475F1 /* PBXTargetDependency */, 172 | ); 173 | name = AsyncSocketDemoTests; 174 | productName = AsyncSocketDemoTests; 175 | productReference = CE44E2231ACE7050007475F1 /* AsyncSocketDemoTests.xctest */; 176 | productType = "com.apple.product-type.bundle.unit-test"; 177 | }; 178 | /* End PBXNativeTarget section */ 179 | 180 | /* Begin PBXProject section */ 181 | CE44E2021ACE704F007475F1 /* Project object */ = { 182 | isa = PBXProject; 183 | attributes = { 184 | LastUpgradeCheck = 0620; 185 | ORGANIZATIONNAME = ligang; 186 | TargetAttributes = { 187 | CE44E2091ACE704F007475F1 = { 188 | CreatedOnToolsVersion = 6.2; 189 | }; 190 | CE44E2221ACE7050007475F1 = { 191 | CreatedOnToolsVersion = 6.2; 192 | TestTargetID = CE44E2091ACE704F007475F1; 193 | }; 194 | }; 195 | }; 196 | buildConfigurationList = CE44E2051ACE704F007475F1 /* Build configuration list for PBXProject "AsyncSocketDemo" */; 197 | compatibilityVersion = "Xcode 3.2"; 198 | developmentRegion = English; 199 | hasScannedForEncodings = 0; 200 | knownRegions = ( 201 | en, 202 | Base, 203 | ); 204 | mainGroup = CE44E2011ACE704F007475F1; 205 | productRefGroup = CE44E20B1ACE7050007475F1 /* Products */; 206 | projectDirPath = ""; 207 | projectRoot = ""; 208 | targets = ( 209 | CE44E2091ACE704F007475F1 /* AsyncSocketDemo */, 210 | CE44E2221ACE7050007475F1 /* AsyncSocketDemoTests */, 211 | ); 212 | }; 213 | /* End PBXProject section */ 214 | 215 | /* Begin PBXResourcesBuildPhase section */ 216 | CE44E2081ACE704F007475F1 /* Resources */ = { 217 | isa = PBXResourcesBuildPhase; 218 | buildActionMask = 2147483647; 219 | files = ( 220 | CE44E2191ACE7050007475F1 /* Main.storyboard in Resources */, 221 | CE44E21E1ACE7050007475F1 /* LaunchScreen.xib in Resources */, 222 | CE44E21B1ACE7050007475F1 /* Images.xcassets in Resources */, 223 | ); 224 | runOnlyForDeploymentPostprocessing = 0; 225 | }; 226 | CE44E2211ACE7050007475F1 /* Resources */ = { 227 | isa = PBXResourcesBuildPhase; 228 | buildActionMask = 2147483647; 229 | files = ( 230 | ); 231 | runOnlyForDeploymentPostprocessing = 0; 232 | }; 233 | /* End PBXResourcesBuildPhase section */ 234 | 235 | /* Begin PBXSourcesBuildPhase section */ 236 | CE44E2061ACE704F007475F1 /* Sources */ = { 237 | isa = PBXSourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | CE44E2161ACE7050007475F1 /* ViewController.m in Sources */, 241 | CE44E23C1ACE77EE007475F1 /* AsyncSocket.m in Sources */, 242 | CE44E2131ACE7050007475F1 /* AppDelegate.m in Sources */, 243 | CE44E2101ACE7050007475F1 /* main.m in Sources */, 244 | CE44E2351ACE7081007475F1 /* LGSocketServe.m in Sources */, 245 | ); 246 | runOnlyForDeploymentPostprocessing = 0; 247 | }; 248 | CE44E21F1ACE7050007475F1 /* Sources */ = { 249 | isa = PBXSourcesBuildPhase; 250 | buildActionMask = 2147483647; 251 | files = ( 252 | CE44E22A1ACE7050007475F1 /* AsyncSocketDemoTests.m in Sources */, 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXSourcesBuildPhase section */ 257 | 258 | /* Begin PBXTargetDependency section */ 259 | CE44E2251ACE7050007475F1 /* PBXTargetDependency */ = { 260 | isa = PBXTargetDependency; 261 | target = CE44E2091ACE704F007475F1 /* AsyncSocketDemo */; 262 | targetProxy = CE44E2241ACE7050007475F1 /* PBXContainerItemProxy */; 263 | }; 264 | /* End PBXTargetDependency section */ 265 | 266 | /* Begin PBXVariantGroup section */ 267 | CE44E2171ACE7050007475F1 /* Main.storyboard */ = { 268 | isa = PBXVariantGroup; 269 | children = ( 270 | CE44E2181ACE7050007475F1 /* Base */, 271 | ); 272 | name = Main.storyboard; 273 | sourceTree = ""; 274 | }; 275 | CE44E21C1ACE7050007475F1 /* LaunchScreen.xib */ = { 276 | isa = PBXVariantGroup; 277 | children = ( 278 | CE44E21D1ACE7050007475F1 /* Base */, 279 | ); 280 | name = LaunchScreen.xib; 281 | sourceTree = ""; 282 | }; 283 | /* End PBXVariantGroup section */ 284 | 285 | /* Begin XCBuildConfiguration section */ 286 | CE44E22B1ACE7050007475F1 /* Debug */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 291 | CLANG_CXX_LIBRARY = "libc++"; 292 | CLANG_ENABLE_MODULES = YES; 293 | CLANG_ENABLE_OBJC_ARC = YES; 294 | CLANG_WARN_BOOL_CONVERSION = YES; 295 | CLANG_WARN_CONSTANT_CONVERSION = YES; 296 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 297 | CLANG_WARN_EMPTY_BODY = YES; 298 | CLANG_WARN_ENUM_CONVERSION = YES; 299 | CLANG_WARN_INT_CONVERSION = YES; 300 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 301 | CLANG_WARN_UNREACHABLE_CODE = YES; 302 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 303 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 304 | COPY_PHASE_STRIP = NO; 305 | ENABLE_STRICT_OBJC_MSGSEND = YES; 306 | GCC_C_LANGUAGE_STANDARD = gnu99; 307 | GCC_DYNAMIC_NO_PIC = NO; 308 | GCC_OPTIMIZATION_LEVEL = 0; 309 | GCC_PREPROCESSOR_DEFINITIONS = ( 310 | "DEBUG=1", 311 | "$(inherited)", 312 | ); 313 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 314 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 315 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 316 | GCC_WARN_UNDECLARED_SELECTOR = YES; 317 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 318 | GCC_WARN_UNUSED_FUNCTION = YES; 319 | GCC_WARN_UNUSED_VARIABLE = YES; 320 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 321 | MTL_ENABLE_DEBUG_INFO = YES; 322 | ONLY_ACTIVE_ARCH = YES; 323 | SDKROOT = iphoneos; 324 | }; 325 | name = Debug; 326 | }; 327 | CE44E22C1ACE7050007475F1 /* Release */ = { 328 | isa = XCBuildConfiguration; 329 | buildSettings = { 330 | ALWAYS_SEARCH_USER_PATHS = NO; 331 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 332 | CLANG_CXX_LIBRARY = "libc++"; 333 | CLANG_ENABLE_MODULES = YES; 334 | CLANG_ENABLE_OBJC_ARC = YES; 335 | CLANG_WARN_BOOL_CONVERSION = YES; 336 | CLANG_WARN_CONSTANT_CONVERSION = YES; 337 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INT_CONVERSION = YES; 341 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 342 | CLANG_WARN_UNREACHABLE_CODE = YES; 343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 344 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 345 | COPY_PHASE_STRIP = NO; 346 | ENABLE_NS_ASSERTIONS = NO; 347 | ENABLE_STRICT_OBJC_MSGSEND = YES; 348 | GCC_C_LANGUAGE_STANDARD = gnu99; 349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 351 | GCC_WARN_UNDECLARED_SELECTOR = YES; 352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 353 | GCC_WARN_UNUSED_FUNCTION = YES; 354 | GCC_WARN_UNUSED_VARIABLE = YES; 355 | IPHONEOS_DEPLOYMENT_TARGET = 8.2; 356 | MTL_ENABLE_DEBUG_INFO = NO; 357 | SDKROOT = iphoneos; 358 | VALIDATE_PRODUCT = YES; 359 | }; 360 | name = Release; 361 | }; 362 | CE44E22E1ACE7050007475F1 /* Debug */ = { 363 | isa = XCBuildConfiguration; 364 | buildSettings = { 365 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 366 | INFOPLIST_FILE = AsyncSocketDemo/Info.plist; 367 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 368 | PRODUCT_NAME = "$(TARGET_NAME)"; 369 | }; 370 | name = Debug; 371 | }; 372 | CE44E22F1ACE7050007475F1 /* Release */ = { 373 | isa = XCBuildConfiguration; 374 | buildSettings = { 375 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 376 | INFOPLIST_FILE = AsyncSocketDemo/Info.plist; 377 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 378 | PRODUCT_NAME = "$(TARGET_NAME)"; 379 | }; 380 | name = Release; 381 | }; 382 | CE44E2311ACE7050007475F1 /* Debug */ = { 383 | isa = XCBuildConfiguration; 384 | buildSettings = { 385 | BUNDLE_LOADER = "$(TEST_HOST)"; 386 | FRAMEWORK_SEARCH_PATHS = ( 387 | "$(SDKROOT)/Developer/Library/Frameworks", 388 | "$(inherited)", 389 | ); 390 | GCC_PREPROCESSOR_DEFINITIONS = ( 391 | "DEBUG=1", 392 | "$(inherited)", 393 | ); 394 | INFOPLIST_FILE = AsyncSocketDemoTests/Info.plist; 395 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 396 | PRODUCT_NAME = "$(TARGET_NAME)"; 397 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncSocketDemo.app/AsyncSocketDemo"; 398 | }; 399 | name = Debug; 400 | }; 401 | CE44E2321ACE7050007475F1 /* Release */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | BUNDLE_LOADER = "$(TEST_HOST)"; 405 | FRAMEWORK_SEARCH_PATHS = ( 406 | "$(SDKROOT)/Developer/Library/Frameworks", 407 | "$(inherited)", 408 | ); 409 | INFOPLIST_FILE = AsyncSocketDemoTests/Info.plist; 410 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 411 | PRODUCT_NAME = "$(TARGET_NAME)"; 412 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/AsyncSocketDemo.app/AsyncSocketDemo"; 413 | }; 414 | name = Release; 415 | }; 416 | /* End XCBuildConfiguration section */ 417 | 418 | /* Begin XCConfigurationList section */ 419 | CE44E2051ACE704F007475F1 /* Build configuration list for PBXProject "AsyncSocketDemo" */ = { 420 | isa = XCConfigurationList; 421 | buildConfigurations = ( 422 | CE44E22B1ACE7050007475F1 /* Debug */, 423 | CE44E22C1ACE7050007475F1 /* Release */, 424 | ); 425 | defaultConfigurationIsVisible = 0; 426 | defaultConfigurationName = Release; 427 | }; 428 | CE44E22D1ACE7050007475F1 /* Build configuration list for PBXNativeTarget "AsyncSocketDemo" */ = { 429 | isa = XCConfigurationList; 430 | buildConfigurations = ( 431 | CE44E22E1ACE7050007475F1 /* Debug */, 432 | CE44E22F1ACE7050007475F1 /* Release */, 433 | ); 434 | defaultConfigurationIsVisible = 0; 435 | }; 436 | CE44E2301ACE7050007475F1 /* Build configuration list for PBXNativeTarget "AsyncSocketDemoTests" */ = { 437 | isa = XCConfigurationList; 438 | buildConfigurations = ( 439 | CE44E2311ACE7050007475F1 /* Debug */, 440 | CE44E2321ACE7050007475F1 /* Release */, 441 | ); 442 | defaultConfigurationIsVisible = 0; 443 | }; 444 | /* End XCConfigurationList section */ 445 | }; 446 | rootObject = CE44E2021ACE704F007475F1 /* Project object */; 447 | } 448 | -------------------------------------------------------------------------------- /AsyncSocketDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AsyncSocketDemo.xcodeproj/project.xcworkspace/xcshareddata/AsyncSocketDemo.xccheckout: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDESourceControlProjectFavoriteDictionaryKey 6 | 7 | IDESourceControlProjectIdentifier 8 | C008F684-75BA-4073-B5B0-D9928E8A5D77 9 | IDESourceControlProjectName 10 | AsyncSocketDemo 11 | IDESourceControlProjectOriginsDictionary 12 | 13 | 90C46CA48C30A98DE2CB9A28F7F2EB135B83BF51 14 | https://github.com/worldligang/AsyncSocketDemo.git 15 | 16 | IDESourceControlProjectPath 17 | AsyncSocketDemo.xcodeproj 18 | IDESourceControlProjectRelativeInstallPathDictionary 19 | 20 | 90C46CA48C30A98DE2CB9A28F7F2EB135B83BF51 21 | ../.. 22 | 23 | IDESourceControlProjectURL 24 | https://github.com/worldligang/AsyncSocketDemo.git 25 | IDESourceControlProjectVersion 26 | 111 27 | IDESourceControlProjectWCCIdentifier 28 | 90C46CA48C30A98DE2CB9A28F7F2EB135B83BF51 29 | IDESourceControlProjectWCConfigurations 30 | 31 | 32 | IDESourceControlRepositoryExtensionIdentifierKey 33 | public.vcs.git 34 | IDESourceControlWCCIdentifierKey 35 | 90C46CA48C30A98DE2CB9A28F7F2EB135B83BF51 36 | IDESourceControlWCCName 37 | AsyncSocketDemo 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AsyncSocketDemo.xcodeproj/xcuserdata/ligang.xcuserdatad/xcschemes/AsyncSocketDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 94 | 96 | 102 | 103 | 104 | 105 | 107 | 108 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /AsyncSocketDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : UIResponder 12 | 13 | @property (strong, nonatomic) UIWindow *window; 14 | 15 | 16 | @end 17 | 18 | -------------------------------------------------------------------------------- /AsyncSocketDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | @interface AppDelegate () 12 | 13 | @end 14 | 15 | @implementation AppDelegate 16 | 17 | 18 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 19 | // Override point for customization after application launch. 20 | return YES; 21 | } 22 | 23 | - (void)applicationWillResignActive:(UIApplication *)application { 24 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 25 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 26 | } 27 | 28 | - (void)applicationDidEnterBackground:(UIApplication *)application { 29 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 30 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 31 | } 32 | 33 | - (void)applicationWillEnterForeground:(UIApplication *)application { 34 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 35 | } 36 | 37 | - (void)applicationDidBecomeActive:(UIApplication *)application { 38 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 39 | } 40 | 41 | - (void)applicationWillTerminate:(UIApplication *)application { 42 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /AsyncSocketDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /AsyncSocketDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /AsyncSocketDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /AsyncSocketDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | ligang.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /AsyncSocketDemo/LGSocketServe.h: -------------------------------------------------------------------------------- 1 | // 2 | // LGSocketServe.h 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AsyncSocket.h" 11 | 12 | 13 | enum{ 14 | SocketOfflineByServer, //服务器掉线 15 | SocketOfflineByUser, //用户断开 16 | SocketOfflineByWifiCut, //wifi 断开 17 | }; 18 | 19 | 20 | @interface LGSocketServe : NSObject 21 | 22 | @property (nonatomic, strong) AsyncSocket *socket; // socket 23 | @property (nonatomic, retain) NSTimer *heartTimer; // 心跳计时器 24 | 25 | + (LGSocketServe *)sharedSocketServe; 26 | 27 | // socket连接 28 | - (void)startConnectSocket; 29 | 30 | // 断开socket连接 31 | -(void)cutOffSocket; 32 | 33 | // 发送消息 34 | - (void)sendMessage:(id)message; 35 | 36 | 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /AsyncSocketDemo/LGSocketServe.m: -------------------------------------------------------------------------------- 1 | // 2 | // LGSocketServe.m 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import "LGSocketServe.h" 10 | 11 | //自己设定 12 | #define HOST @"192.168.0.1" 13 | #define PORT 8080 14 | 15 | //设置连接超时 16 | #define TIME_OUT 20 17 | 18 | //设置读取超时 -1 表示不会使用超时 19 | #define READ_TIME_OUT -1 20 | 21 | //设置写入超时 -1 表示不会使用超时 22 | #define WRITE_TIME_OUT -1 23 | 24 | //每次最多读取多少 25 | #define MAX_BUFFER 1024 26 | 27 | 28 | @implementation LGSocketServe 29 | 30 | 31 | static LGSocketServe *socketServe = nil; 32 | 33 | #pragma mark public static methods 34 | 35 | 36 | + (LGSocketServe *)sharedSocketServe { 37 | @synchronized(self) { 38 | if(socketServe == nil) { 39 | socketServe = [[[self class] alloc] init]; 40 | } 41 | } 42 | return socketServe; 43 | } 44 | 45 | 46 | +(id)allocWithZone:(NSZone *)zone 47 | { 48 | @synchronized(self) 49 | { 50 | if (socketServe == nil) 51 | { 52 | socketServe = [super allocWithZone:zone]; 53 | return socketServe; 54 | } 55 | } 56 | return nil; 57 | } 58 | 59 | 60 | - (void)startConnectSocket 61 | { 62 | self.socket = [[AsyncSocket alloc] initWithDelegate:self]; 63 | [self.socket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 64 | if ( ![self SocketOpen:HOST port:PORT] ) 65 | { 66 | 67 | } 68 | 69 | } 70 | 71 | - (NSInteger)SocketOpen:(NSString*)addr port:(NSInteger)port 72 | { 73 | 74 | if (![self.socket isConnected]) 75 | { 76 | NSError *error = nil; 77 | [self.socket connectToHost:addr onPort:port withTimeout:TIME_OUT error:&error]; 78 | } 79 | 80 | return 0; 81 | } 82 | 83 | 84 | -(void)cutOffSocket 85 | { 86 | self.socket.userData = SocketOfflineByUser; 87 | [self.socket disconnect]; 88 | } 89 | 90 | 91 | - (void)sendMessage:(id)message 92 | { 93 | //像服务器发送数据 94 | NSData *cmdData = [message dataUsingEncoding:NSUTF8StringEncoding]; 95 | [self.socket writeData:cmdData withTimeout:WRITE_TIME_OUT tag:1]; 96 | } 97 | 98 | 99 | 100 | 101 | 102 | #pragma mark - Delegate 103 | 104 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock 105 | { 106 | 107 | NSLog(@"7878 sorry the connect is failure %ld",sock.userData); 108 | 109 | if (sock.userData == SocketOfflineByServer) { 110 | // 服务器掉线,重连 111 | [self startConnectSocket]; 112 | } 113 | else if (sock.userData == SocketOfflineByUser) { 114 | 115 | // 如果由用户断开,不进行重连 116 | return; 117 | }else if (sock.userData == SocketOfflineByWifiCut) { 118 | 119 | // wifi断开,不进行重连 120 | return; 121 | } 122 | 123 | } 124 | 125 | 126 | 127 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err 128 | { 129 | NSData * unreadData = [sock unreadData]; // ** This gets the current buffer 130 | if(unreadData.length > 0) { 131 | [self onSocket:sock didReadData:unreadData withTag:0]; // ** Return as much data that could be collected 132 | } else { 133 | 134 | NSLog(@" willDisconnectWithError %ld err = %@",sock.userData,[err description]); 135 | if (err.code == 57) { 136 | self.socket.userData = SocketOfflineByWifiCut; 137 | } 138 | } 139 | 140 | } 141 | 142 | 143 | 144 | - (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket 145 | { 146 | NSLog(@"didAcceptNewSocket"); 147 | } 148 | 149 | 150 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port 151 | { 152 | //这是异步返回的连接成功, 153 | NSLog(@"didConnectToHost"); 154 | 155 | //通过定时器不断发送消息,来检测长连接 156 | self.heartTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(checkLongConnectByServe) userInfo:nil repeats:YES]; 157 | [self.heartTimer fire]; 158 | } 159 | 160 | // 心跳连接 161 | -(void)checkLongConnectByServe{ 162 | 163 | // 向服务器发送固定可是的消息,来检测长连接 164 | NSString *longConnect = @"connect is here"; 165 | NSData *data = [longConnect dataUsingEncoding:NSUTF8StringEncoding]; 166 | [self.socket writeData:data withTimeout:1 tag:1]; 167 | } 168 | 169 | 170 | 171 | //接受消息成功之后回调 172 | - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 173 | { 174 | //服务端返回消息数据量比较大时,可能分多次返回。所以在读取消息的时候,设置MAX_BUFFER表示每次最多读取多少,当data.length < MAX_BUFFER我们认为有可能是接受完一个完整的消息,然后才解析 175 | if( data.length < MAX_BUFFER ) 176 | { 177 | //收到结果解析... 178 | NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; 179 | NSLog(@"%@",dic); 180 | //解析出来的消息,可以通过通知、代理、block等传出去 181 | 182 | } 183 | 184 | 185 | [self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0]; 186 | 187 | } 188 | 189 | 190 | //发送消息成功之后回调 191 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag 192 | { 193 | //读取消息 194 | [self.socket readDataWithTimeout:-1 buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0]; 195 | } 196 | 197 | 198 | 199 | 200 | 201 | @end 202 | -------------------------------------------------------------------------------- /AsyncSocketDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /AsyncSocketDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "LGSocketServe.h" 11 | 12 | @interface ViewController () 13 | 14 | @end 15 | 16 | @implementation ViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // Do any additional setup after loading the view, typically from a nib. 21 | 22 | 23 | LGSocketServe *socketServe = [LGSocketServe sharedSocketServe]; 24 | //socket连接前先断开连接以免之前socket连接没有断开导致闪退 25 | [socketServe cutOffSocket]; 26 | socketServe.socket.userData = SocketOfflineByServer; 27 | [socketServe startConnectSocket]; 28 | 29 | //发送消息 @"hello world"只是举个列子,具体根据服务端的消息格式 30 | [socketServe sendMessage:@"hello world"]; 31 | 32 | } 33 | 34 | - (void)didReceiveMemoryWarning { 35 | [super didReceiveMemoryWarning]; 36 | // Dispose of any resources that can be recreated. 37 | } 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /AsyncSocketDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // AsyncSocketDemo 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /AsyncSocketDemo/socket/AsyncSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSocket.h 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Dustin Voss on Wed Jan 29 2003. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #import 12 | 13 | @class AsyncSocket; 14 | @class AsyncReadPacket; 15 | @class AsyncWritePacket; 16 | 17 | extern NSString *const AsyncSocketException; 18 | extern NSString *const AsyncSocketErrorDomain; 19 | 20 | enum AsyncSocketError 21 | { 22 | AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. 23 | AsyncSocketNoError = 0, // Never used. 24 | AsyncSocketCanceledError, // onSocketWillConnect: returned NO. 25 | AsyncSocketConnectTimeoutError, 26 | AsyncSocketReadMaxedOutError, // Reached set maxLength without completing 27 | AsyncSocketReadTimeoutError, 28 | AsyncSocketWriteTimeoutError 29 | }; 30 | typedef enum AsyncSocketError AsyncSocketError; 31 | 32 | @protocol AsyncSocketDelegate 33 | @optional 34 | 35 | /** 36 | * In the event of an error, the socket is closed. 37 | * You may call "unreadData" during this call-back to get the last bit of data off the socket. 38 | * When connecting, this delegate method may be called 39 | * before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". 40 | **/ 41 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; 42 | 43 | /** 44 | * Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, 45 | * do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". 46 | * 47 | * If you call the disconnect method, and the socket wasn't already disconnected, 48 | * this delegate method will be called before the disconnect method returns. 49 | **/ 50 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock; 51 | 52 | /** 53 | * Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have 54 | * the same delegate and will call "onSocket:didConnectToHost:port:". 55 | **/ 56 | - (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; 57 | 58 | /** 59 | * Called when a new socket is spawned to handle a connection. This method should return the run-loop of the 60 | * thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. 61 | **/ 62 | - (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; 63 | 64 | /** 65 | * Called when a socket is about to connect. This method should return YES to continue, or NO to abort. 66 | * If aborted, will result in AsyncSocketCanceledError. 67 | * 68 | * If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the 69 | * CFReadStream and CFWriteStream as desired prior to connection. 70 | * 71 | * If the connectToAddress:error: method was called, the delegate will be able to access and configure the 72 | * CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and 73 | * configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. 74 | **/ 75 | - (BOOL)onSocketWillConnect:(AsyncSocket *)sock; 76 | 77 | /** 78 | * Called when a socket connects and is ready for reading and writing. 79 | * The host parameter will be an IP address, not a DNS name. 80 | **/ 81 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; 82 | 83 | /** 84 | * Called when a socket has completed reading the requested data into memory. 85 | * Not called if there is an error. 86 | **/ 87 | - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; 88 | 89 | /** 90 | * Called when a socket has read in data, but has not yet completed the read. 91 | * This would occur if using readToData: or readToLength: methods. 92 | * It may be used to for things such as updating progress bars. 93 | **/ 94 | - (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 95 | 96 | /** 97 | * Called when a socket has completed writing the requested data. Not called if there is an error. 98 | **/ 99 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; 100 | 101 | /** 102 | * Called when a socket has written some data, but has not yet completed the entire write. 103 | * It may be used to for things such as updating progress bars. 104 | **/ 105 | - (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag; 106 | 107 | /** 108 | * Called if a read operation has reached its timeout without completing. 109 | * This method allows you to optionally extend the timeout. 110 | * If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. 111 | * If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. 112 | * 113 | * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. 114 | * The length parameter is the number of bytes that have been read so far for the read operation. 115 | * 116 | * Note that this method may be called multiple times for a single read if you return positive numbers. 117 | **/ 118 | - (NSTimeInterval)onSocket:(AsyncSocket *)sock 119 | shouldTimeoutReadWithTag:(long)tag 120 | elapsed:(NSTimeInterval)elapsed 121 | bytesDone:(NSUInteger)length; 122 | 123 | /** 124 | * Called if a write operation has reached its timeout without completing. 125 | * This method allows you to optionally extend the timeout. 126 | * If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. 127 | * If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. 128 | * 129 | * The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. 130 | * The length parameter is the number of bytes that have been written so far for the write operation. 131 | * 132 | * Note that this method may be called multiple times for a single write if you return positive numbers. 133 | **/ 134 | - (NSTimeInterval)onSocket:(AsyncSocket *)sock 135 | shouldTimeoutWriteWithTag:(long)tag 136 | elapsed:(NSTimeInterval)elapsed 137 | bytesDone:(NSUInteger)length; 138 | 139 | /** 140 | * Called after the socket has successfully completed SSL/TLS negotiation. 141 | * This method is not called unless you use the provided startTLS method. 142 | * 143 | * If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, 144 | * and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. 145 | **/ 146 | - (void)onSocketDidSecure:(AsyncSocket *)sock; 147 | 148 | @end 149 | 150 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 151 | #pragma mark - 152 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 153 | 154 | @interface AsyncSocket : NSObject 155 | { 156 | CFSocketNativeHandle theNativeSocket4; 157 | CFSocketNativeHandle theNativeSocket6; 158 | 159 | CFSocketRef theSocket4; // IPv4 accept or connect socket 160 | CFSocketRef theSocket6; // IPv6 accept or connect socket 161 | 162 | CFReadStreamRef theReadStream; 163 | CFWriteStreamRef theWriteStream; 164 | 165 | CFRunLoopSourceRef theSource4; // For theSocket4 166 | CFRunLoopSourceRef theSource6; // For theSocket6 167 | CFRunLoopRef theRunLoop; 168 | CFSocketContext theContext; 169 | NSArray *theRunLoopModes; 170 | 171 | NSTimer *theConnectTimer; 172 | 173 | NSMutableArray *theReadQueue; 174 | AsyncReadPacket *theCurrentRead; 175 | NSTimer *theReadTimer; 176 | NSMutableData *partialReadBuffer; 177 | 178 | NSMutableArray *theWriteQueue; 179 | AsyncWritePacket *theCurrentWrite; 180 | NSTimer *theWriteTimer; 181 | 182 | id theDelegate; 183 | UInt16 theFlags; 184 | 185 | long theUserData; 186 | } 187 | 188 | - (id)init; 189 | - (id)initWithDelegate:(id)delegate; 190 | - (id)initWithDelegate:(id)delegate userData:(long)userData; 191 | 192 | /* String representation is long but has no "\n". */ 193 | - (NSString *)description; 194 | 195 | /** 196 | * Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate 197 | * before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. 198 | **/ 199 | - (id)delegate; 200 | - (BOOL)canSafelySetDelegate; 201 | - (void)setDelegate:(id)delegate; 202 | 203 | /* User data can be a long, or an id or void * cast to a long. */ 204 | - (long)userData; 205 | - (void)setUserData:(long)userData; 206 | 207 | /* Don't use these to read or write. And don't close them either! */ 208 | - (CFSocketRef)getCFSocket; 209 | - (CFReadStreamRef)getCFReadStream; 210 | - (CFWriteStreamRef)getCFWriteStream; 211 | 212 | // Once one of the accept or connect methods are called, the AsyncSocket instance is locked in 213 | // and the other accept/connect methods can't be called without disconnecting the socket first. 214 | // If the attempt fails or times out, these methods either return NO or 215 | // call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". 216 | 217 | // When an incoming connection is accepted, AsyncSocket invokes several delegate methods. 218 | // These methods are (in chronological order): 219 | // 1. onSocket:didAcceptNewSocket: 220 | // 2. onSocket:wantsRunLoopForNewSocket: 221 | // 3. onSocketWillConnect: 222 | // 223 | // Your server code will need to retain the accepted socket (if you want to accept it). 224 | // The best place to do this is probably in the onSocket:didAcceptNewSocket: method. 225 | // 226 | // After the read and write streams have been setup for the newly accepted socket, 227 | // the onSocket:didConnectToHost:port: method will be called on the proper run loop. 228 | // 229 | // Multithreading Note: If you're going to be moving the newly accepted socket to another run 230 | // loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the 231 | // onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. 232 | // Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. 233 | 234 | /** 235 | * Tells the socket to begin listening and accepting connections on the given port. 236 | * When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). 237 | * The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) 238 | **/ 239 | - (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; 240 | 241 | /** 242 | * This method is the same as acceptOnPort:error: with the additional option 243 | * of specifying which interface to listen on. So, for example, if you were writing code for a server that 244 | * has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it 245 | * to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. 246 | * You may also use the special strings "localhost" or "loopback" to specify that 247 | * the socket only accept connections from the local machine. 248 | * 249 | * To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. 250 | **/ 251 | - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; 252 | 253 | /** 254 | * Connects to the given host and port. 255 | * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") 256 | **/ 257 | - (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; 258 | 259 | /** 260 | * This method is the same as connectToHost:onPort:error: with an additional timeout option. 261 | * To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. 262 | **/ 263 | - (BOOL)connectToHost:(NSString *)hostname 264 | onPort:(UInt16)port 265 | withTimeout:(NSTimeInterval)timeout 266 | error:(NSError **)errPtr; 267 | 268 | /** 269 | * Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. 270 | * For example, a NSData object returned from NSNetService's addresses method. 271 | * 272 | * If you have an existing struct sockaddr you can convert it to a NSData object like so: 273 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; 274 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; 275 | **/ 276 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 277 | 278 | /** 279 | * This method is the same as connectToAddress:error: with an additional timeout option. 280 | * To not time out use a negative time interval, or simply use the connectToAddress:error: method. 281 | **/ 282 | - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; 283 | 284 | - (BOOL)connectToAddress:(NSData *)remoteAddr 285 | viaInterfaceAddress:(NSData *)interfaceAddr 286 | withTimeout:(NSTimeInterval)timeout 287 | error:(NSError **)errPtr; 288 | 289 | /** 290 | * Disconnects immediately. Any pending reads or writes are dropped. 291 | * If the socket is not already disconnected, the onSocketDidDisconnect delegate method 292 | * will be called immediately, before this method returns. 293 | * 294 | * Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) 295 | * [asyncSocket setDelegate:nil]; 296 | * [asyncSocket disconnect]; 297 | * [asyncSocket release]; 298 | **/ 299 | - (void)disconnect; 300 | 301 | /** 302 | * Disconnects after all pending reads have completed. 303 | * After calling this, the read and write methods will do nothing. 304 | * The socket will disconnect even if there are still pending writes. 305 | **/ 306 | - (void)disconnectAfterReading; 307 | 308 | /** 309 | * Disconnects after all pending writes have completed. 310 | * After calling this, the read and write methods will do nothing. 311 | * The socket will disconnect even if there are still pending reads. 312 | **/ 313 | - (void)disconnectAfterWriting; 314 | 315 | /** 316 | * Disconnects after all pending reads and writes have completed. 317 | * After calling this, the read and write methods will do nothing. 318 | **/ 319 | - (void)disconnectAfterReadingAndWriting; 320 | 321 | /* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ 322 | - (BOOL)isConnected; 323 | 324 | /** 325 | * Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. 326 | * The host will be an IP address. 327 | **/ 328 | - (NSString *)connectedHost; 329 | - (UInt16)connectedPort; 330 | 331 | - (NSString *)localHost; 332 | - (UInt16)localPort; 333 | 334 | /** 335 | * Returns the local or remote address to which this socket is connected, 336 | * specified as a sockaddr structure wrapped in a NSData object. 337 | * 338 | * See also the connectedHost, connectedPort, localHost and localPort methods. 339 | **/ 340 | - (NSData *)connectedAddress; 341 | - (NSData *)localAddress; 342 | 343 | /** 344 | * Returns whether the socket is IPv4 or IPv6. 345 | * An accepting socket may be both. 346 | **/ 347 | - (BOOL)isIPv4; 348 | - (BOOL)isIPv6; 349 | 350 | // The readData and writeData methods won't block (they are asynchronous). 351 | // 352 | // When a read is complete the onSocket:didReadData:withTag: delegate method is called. 353 | // When a write is complete the onSocket:didWriteDataWithTag: delegate method is called. 354 | // 355 | // You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) 356 | // If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method 357 | // is called to optionally allow you to extend the timeout. 358 | // Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". 359 | // 360 | // The tag is for your convenience. 361 | // You can use it as an array index, step number, state id, pointer, etc. 362 | 363 | /** 364 | * Reads the first available bytes that become available on the socket. 365 | * 366 | * If the timeout value is negative, the read operation will not use a timeout. 367 | **/ 368 | - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; 369 | 370 | /** 371 | * Reads the first available bytes that become available on the socket. 372 | * The bytes will be appended to the given byte buffer starting at the given offset. 373 | * The given buffer will automatically be increased in size if needed. 374 | * 375 | * If the timeout value is negative, the read operation will not use a timeout. 376 | * If the buffer if nil, the socket will create a buffer for you. 377 | * 378 | * If the bufferOffset is greater than the length of the given buffer, 379 | * the method will do nothing, and the delegate will not be called. 380 | * 381 | * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. 382 | * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. 383 | * That is, it will reference the bytes that were appended to the given buffer. 384 | **/ 385 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 386 | buffer:(NSMutableData *)buffer 387 | bufferOffset:(NSUInteger)offset 388 | tag:(long)tag; 389 | 390 | /** 391 | * Reads the first available bytes that become available on the socket. 392 | * The bytes will be appended to the given byte buffer starting at the given offset. 393 | * The given buffer will automatically be increased in size if needed. 394 | * A maximum of length bytes will be read. 395 | * 396 | * If the timeout value is negative, the read operation will not use a timeout. 397 | * If the buffer if nil, a buffer will automatically be created for you. 398 | * If maxLength is zero, no length restriction is enforced. 399 | * 400 | * If the bufferOffset is greater than the length of the given buffer, 401 | * the method will do nothing, and the delegate will not be called. 402 | * 403 | * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. 404 | * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. 405 | * That is, it will reference the bytes that were appended to the given buffer. 406 | **/ 407 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 408 | buffer:(NSMutableData *)buffer 409 | bufferOffset:(NSUInteger)offset 410 | maxLength:(NSUInteger)length 411 | tag:(long)tag; 412 | 413 | /** 414 | * Reads the given number of bytes. 415 | * 416 | * If the timeout value is negative, the read operation will not use a timeout. 417 | * 418 | * If the length is 0, this method does nothing and the delegate is not called. 419 | **/ 420 | - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; 421 | 422 | /** 423 | * Reads the given number of bytes. 424 | * The bytes will be appended to the given byte buffer starting at the given offset. 425 | * The given buffer will automatically be increased in size if needed. 426 | * 427 | * If the timeout value is negative, the read operation will not use a timeout. 428 | * If the buffer if nil, a buffer will automatically be created for you. 429 | * 430 | * If the length is 0, this method does nothing and the delegate is not called. 431 | * If the bufferOffset is greater than the length of the given buffer, 432 | * the method will do nothing, and the delegate will not be called. 433 | * 434 | * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. 435 | * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. 436 | * That is, it will reference the bytes that were appended to the given buffer. 437 | **/ 438 | - (void)readDataToLength:(NSUInteger)length 439 | withTimeout:(NSTimeInterval)timeout 440 | buffer:(NSMutableData *)buffer 441 | bufferOffset:(NSUInteger)offset 442 | tag:(long)tag; 443 | 444 | /** 445 | * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. 446 | * 447 | * If the timeout value is negative, the read operation will not use a timeout. 448 | * 449 | * If you pass nil or zero-length data as the "data" parameter, 450 | * the method will do nothing, and the delegate will not be called. 451 | * 452 | * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. 453 | * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for 454 | * a character, the read will prematurely end. 455 | **/ 456 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 457 | 458 | /** 459 | * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. 460 | * The bytes will be appended to the given byte buffer starting at the given offset. 461 | * The given buffer will automatically be increased in size if needed. 462 | * 463 | * If the timeout value is negative, the read operation will not use a timeout. 464 | * If the buffer if nil, a buffer will automatically be created for you. 465 | * 466 | * If the bufferOffset is greater than the length of the given buffer, 467 | * the method will do nothing, and the delegate will not be called. 468 | * 469 | * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. 470 | * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. 471 | * That is, it will reference the bytes that were appended to the given buffer. 472 | * 473 | * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. 474 | * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for 475 | * a character, the read will prematurely end. 476 | **/ 477 | - (void)readDataToData:(NSData *)data 478 | withTimeout:(NSTimeInterval)timeout 479 | buffer:(NSMutableData *)buffer 480 | bufferOffset:(NSUInteger)offset 481 | tag:(long)tag; 482 | 483 | /** 484 | * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. 485 | * 486 | * If the timeout value is negative, the read operation will not use a timeout. 487 | * 488 | * If maxLength is zero, no length restriction is enforced. 489 | * Otherwise if maxLength bytes are read without completing the read, 490 | * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. 491 | * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. 492 | * 493 | * If you pass nil or zero-length data as the "data" parameter, 494 | * the method will do nothing, and the delegate will not be called. 495 | * If you pass a maxLength parameter that is less than the length of the data parameter, 496 | * the method will do nothing, and the delegate will not be called. 497 | * 498 | * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. 499 | * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for 500 | * a character, the read will prematurely end. 501 | **/ 502 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag; 503 | 504 | /** 505 | * Reads bytes until (and including) the passed "data" parameter, which acts as a separator. 506 | * The bytes will be appended to the given byte buffer starting at the given offset. 507 | * The given buffer will automatically be increased in size if needed. 508 | * A maximum of length bytes will be read. 509 | * 510 | * If the timeout value is negative, the read operation will not use a timeout. 511 | * If the buffer if nil, a buffer will automatically be created for you. 512 | * 513 | * If maxLength is zero, no length restriction is enforced. 514 | * Otherwise if maxLength bytes are read without completing the read, 515 | * it is treated similarly to a timeout - the socket is closed with a AsyncSocketReadMaxedOutError. 516 | * The read will complete successfully if exactly maxLength bytes are read and the given data is found at the end. 517 | * 518 | * If you pass a maxLength parameter that is less than the length of the data parameter, 519 | * the method will do nothing, and the delegate will not be called. 520 | * If the bufferOffset is greater than the length of the given buffer, 521 | * the method will do nothing, and the delegate will not be called. 522 | * 523 | * If you pass a buffer, you must not alter it in any way while AsyncSocket is using it. 524 | * After completion, the data returned in onSocket:didReadData:withTag: will be a subset of the given buffer. 525 | * That is, it will reference the bytes that were appended to the given buffer. 526 | * 527 | * To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. 528 | * Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for 529 | * a character, the read will prematurely end. 530 | **/ 531 | - (void)readDataToData:(NSData *)data 532 | withTimeout:(NSTimeInterval)timeout 533 | buffer:(NSMutableData *)buffer 534 | bufferOffset:(NSUInteger)offset 535 | maxLength:(NSUInteger)length 536 | tag:(long)tag; 537 | 538 | /** 539 | * Writes data to the socket, and calls the delegate when finished. 540 | * 541 | * If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. 542 | * If the timeout value is negative, the write operation will not use a timeout. 543 | **/ 544 | - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 545 | 546 | /** 547 | * Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). 548 | * "tag", "done" and "total" will be filled in if they aren't NULL. 549 | **/ 550 | - (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; 551 | - (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total; 552 | 553 | /** 554 | * Secures the connection using SSL/TLS. 555 | * 556 | * This method may be called at any time, and the TLS handshake will occur after all pending reads and writes 557 | * are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing 558 | * the upgrade to TLS at the same time, without having to wait for the write to finish. 559 | * Any reads or writes scheduled after this method is called will occur over the secured connection. 560 | * 561 | * The possible keys and values for the TLS settings are well documented. 562 | * Some possible keys are: 563 | * - kCFStreamSSLLevel 564 | * - kCFStreamSSLAllowsExpiredCertificates 565 | * - kCFStreamSSLAllowsExpiredRoots 566 | * - kCFStreamSSLAllowsAnyRoot 567 | * - kCFStreamSSLValidatesCertificateChain 568 | * - kCFStreamSSLPeerName 569 | * - kCFStreamSSLCertificates 570 | * - kCFStreamSSLIsServer 571 | * 572 | * Please refer to Apple's documentation for associated values, as well as other possible keys. 573 | * 574 | * If you pass in nil or an empty dictionary, the default settings will be used. 575 | * 576 | * The default settings will check to make sure the remote party's certificate is signed by a 577 | * trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. 578 | * However it will not verify the name on the certificate unless you 579 | * give it a name to verify against via the kCFStreamSSLPeerName key. 580 | * The security implications of this are important to understand. 581 | * Imagine you are attempting to create a secure connection to MySecureServer.com, 582 | * but your socket gets directed to MaliciousServer.com because of a hacked DNS server. 583 | * If you simply use the default settings, and MaliciousServer.com has a valid certificate, 584 | * the default settings will not detect any problems since the certificate is valid. 585 | * To properly secure your connection in this particular scenario you 586 | * should set the kCFStreamSSLPeerName property to "MySecureServer.com". 587 | * If you do not know the peer name of the remote host in advance (for example, you're not sure 588 | * if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the 589 | * certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. 590 | * The X509Certificate class is part of the CocoaAsyncSocket open source project. 591 | **/ 592 | - (void)startTLS:(NSDictionary *)tlsSettings; 593 | 594 | /** 595 | * For handling readDataToData requests, data is necessarily read from the socket in small increments. 596 | * The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and 597 | * store any overflow in a small internal buffer. 598 | * This is termed pre-buffering, as some data may be read for you before you ask for it. 599 | * If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. 600 | * 601 | * The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. 602 | * It is highly recommended one leave this set to YES. 603 | * 604 | * This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. 605 | * In that case, this method exists to allow one to easily enable pre-buffering when ready. 606 | **/ 607 | - (void)enablePreBuffering; 608 | 609 | /** 610 | * When you create an AsyncSocket, it is added to the runloop of the current thread. 611 | * So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. 612 | * 613 | * If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to 614 | * allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. 615 | * 616 | * If, however, you need to move the socket to a separate thread at a later time, this 617 | * method may be used to accomplish the task. 618 | * 619 | * This method must be called from the thread/runloop the socket is currently running on. 620 | * 621 | * Note: After calling this method, all further method calls to this object should be done from the given runloop. 622 | * Also, all delegate calls will be sent on the given runloop. 623 | **/ 624 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; 625 | 626 | /** 627 | * Allows you to configure which run loop modes the socket uses. 628 | * The default set of run loop modes is NSDefaultRunLoopMode. 629 | * 630 | * If you'd like your socket to continue operation during other modes, you may want to add modes such as 631 | * NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. 632 | * 633 | * Accepted sockets will automatically inherit the same run loop modes as the listening socket. 634 | * 635 | * Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. 636 | **/ 637 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes; 638 | - (BOOL)addRunLoopMode:(NSString *)runLoopMode; 639 | - (BOOL)removeRunLoopMode:(NSString *)runLoopMode; 640 | 641 | /** 642 | * Returns the current run loop modes the AsyncSocket instance is operating in. 643 | * The default set of run loop modes is NSDefaultRunLoopMode. 644 | **/ 645 | - (NSArray *)runLoopModes; 646 | 647 | /** 648 | * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read 649 | * any data that's left on the socket. 650 | **/ 651 | - (NSData *)unreadData; 652 | 653 | /* A few common line separators, for use with the readDataToData:... methods. */ 654 | + (NSData *)CRLFData; // 0x0D0A 655 | + (NSData *)CRData; // 0x0D 656 | + (NSData *)LFData; // 0x0A 657 | + (NSData *)ZeroData; // 0x00 658 | 659 | @end 660 | -------------------------------------------------------------------------------- /AsyncSocketDemo/socket/AsyncSocket.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSocket.m 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Dustin Voss on Wed Jan 29 2003. 6 | // Updated and maintained by Deusty Designs and the Mac development community. 7 | // 8 | // http://code.google.com/p/cocoaasyncsocket/ 9 | // 10 | 11 | #if ! __has_feature(objc_arc) 12 | #warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC). 13 | #endif 14 | 15 | #import "AsyncSocket.h" 16 | #import 17 | #import 18 | #import 19 | #import 20 | #import 21 | 22 | #if TARGET_OS_IPHONE 23 | // Note: You may need to add the CFNetwork Framework to your project 24 | #import 25 | #endif 26 | 27 | #pragma mark Declarations 28 | 29 | #define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default 30 | 31 | #define READQUEUE_CAPACITY 5 // Initial capacity 32 | #define WRITEQUEUE_CAPACITY 5 // Initial capacity 33 | #define READALL_CHUNKSIZE 256 // Incremental increase in buffer size 34 | #define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass 35 | 36 | // AsyncSocket is RunLoop based, and is thus not thread-safe. 37 | // You must always access your AsyncSocket instance from the thread/runloop in which the instance is running. 38 | // You can use methods such as performSelectorOnThread to accomplish this. 39 | // Failure to comply with these thread-safety rules may result in errors. 40 | // You can enable this option to help diagnose where you are incorrectly accessing your socket. 41 | #if DEBUG 42 | #define DEBUG_THREAD_SAFETY 1 43 | #else 44 | #define DEBUG_THREAD_SAFETY 0 45 | #endif 46 | // 47 | // If you constantly need to access your socket from multiple threads 48 | // then you may consider using GCDAsyncSocket instead, which is thread-safe. 49 | 50 | NSString *const AsyncSocketException = @"AsyncSocketException"; 51 | NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; 52 | 53 | 54 | enum AsyncSocketFlags 55 | { 56 | kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled 57 | kDidStartDelegate = 1 << 1, // If set, disconnection results in delegate call 58 | kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream 59 | kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream 60 | kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete 61 | kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete 62 | kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed 63 | kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued 64 | kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued 65 | kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error 66 | kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled 67 | kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled 68 | kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown. 69 | kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown. 70 | }; 71 | 72 | @interface AsyncSocket (Private) 73 | 74 | // Connecting 75 | - (void)startConnectTimeout:(NSTimeInterval)timeout; 76 | - (void)endConnectTimeout; 77 | - (void)doConnectTimeout:(NSTimer *)timer; 78 | 79 | // Socket Implementation 80 | - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; 81 | - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 82 | - (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr; 83 | - (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; 84 | - (BOOL)configureSocketAndReturnError:(NSError **)errPtr; 85 | - (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 86 | - (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket; 87 | - (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err; 88 | 89 | // Stream Implementation 90 | - (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; 91 | - (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; 92 | - (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; 93 | - (BOOL)configureStreamsAndReturnError:(NSError **)errPtr; 94 | - (BOOL)openStreamsAndReturnError:(NSError **)errPtr; 95 | - (void)doStreamOpen; 96 | - (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr; 97 | 98 | // Disconnect Implementation 99 | - (void)closeWithError:(NSError *)err; 100 | - (void)recoverUnreadData; 101 | - (void)emptyQueues; 102 | - (void)close; 103 | 104 | // Errors 105 | - (NSError *)getErrnoError; 106 | - (NSError *)getAbortError; 107 | - (NSError *)getStreamError; 108 | - (NSError *)getSocketError; 109 | - (NSError *)getConnectTimeoutError; 110 | - (NSError *)getReadMaxedOutError; 111 | - (NSError *)getReadTimeoutError; 112 | - (NSError *)getWriteTimeoutError; 113 | - (NSError *)errorFromCFStreamError:(CFStreamError)err; 114 | 115 | // Diagnostics 116 | - (BOOL)isDisconnected; 117 | - (BOOL)areStreamsConnected; 118 | - (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; 119 | - (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; 120 | - (NSString *)connectedHostFromCFSocket4:(CFSocketRef)socket; 121 | - (NSString *)connectedHostFromCFSocket6:(CFSocketRef)socket; 122 | - (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; 123 | - (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; 124 | - (UInt16)connectedPortFromCFSocket4:(CFSocketRef)socket; 125 | - (UInt16)connectedPortFromCFSocket6:(CFSocketRef)socket; 126 | - (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; 127 | - (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; 128 | - (NSString *)localHostFromCFSocket4:(CFSocketRef)socket; 129 | - (NSString *)localHostFromCFSocket6:(CFSocketRef)socket; 130 | - (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket; 131 | - (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket; 132 | - (UInt16)localPortFromCFSocket4:(CFSocketRef)socket; 133 | - (UInt16)localPortFromCFSocket6:(CFSocketRef)socket; 134 | - (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4; 135 | - (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6; 136 | - (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4; 137 | - (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6; 138 | 139 | // Reading 140 | - (void)doBytesAvailable; 141 | - (void)completeCurrentRead; 142 | - (void)endCurrentRead; 143 | - (void)scheduleDequeueRead; 144 | - (void)maybeDequeueRead; 145 | - (void)doReadTimeout:(NSTimer *)timer; 146 | 147 | // Writing 148 | - (void)doSendBytes; 149 | - (void)completeCurrentWrite; 150 | - (void)endCurrentWrite; 151 | - (void)scheduleDequeueWrite; 152 | - (void)maybeDequeueWrite; 153 | - (void)maybeScheduleDisconnect; 154 | - (void)doWriteTimeout:(NSTimer *)timer; 155 | 156 | // Run Loop 157 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source; 158 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; 159 | - (void)runLoopAddTimer:(NSTimer *)timer; 160 | - (void)runLoopRemoveTimer:(NSTimer *)timer; 161 | - (void)runLoopUnscheduleReadStream; 162 | - (void)runLoopUnscheduleWriteStream; 163 | 164 | // Security 165 | - (void)maybeStartTLS; 166 | - (void)onTLSHandshakeSuccessful; 167 | 168 | // Callbacks 169 | - (void)doCFCallback:(CFSocketCallBackType)type 170 | forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; 171 | - (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; 172 | - (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; 173 | 174 | @end 175 | 176 | static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); 177 | static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo); 178 | static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); 179 | 180 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 181 | #pragma mark - 182 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 183 | 184 | /** 185 | * The AsyncReadPacket encompasses the instructions for any given read. 186 | * The content of a read packet allows the code to determine if we're: 187 | * - reading to a certain length 188 | * - reading to a certain separator 189 | * - or simply reading the first chunk of available data 190 | **/ 191 | @interface AsyncReadPacket : NSObject 192 | { 193 | @public 194 | NSMutableData *buffer; 195 | NSUInteger startOffset; 196 | NSUInteger bytesDone; 197 | NSUInteger maxLength; 198 | NSTimeInterval timeout; 199 | NSUInteger readLength; 200 | NSData *term; 201 | BOOL bufferOwner; 202 | NSUInteger originalBufferLength; 203 | long tag; 204 | } 205 | - (id)initWithData:(NSMutableData *)d 206 | startOffset:(NSUInteger)s 207 | maxLength:(NSUInteger)m 208 | timeout:(NSTimeInterval)t 209 | readLength:(NSUInteger)l 210 | terminator:(NSData *)e 211 | tag:(long)i; 212 | 213 | - (NSUInteger)readLengthForNonTerm; 214 | - (NSUInteger)readLengthForTerm; 215 | - (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr; 216 | 217 | - (NSUInteger)prebufferReadLengthForTerm; 218 | - (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes; 219 | @end 220 | 221 | @implementation AsyncReadPacket 222 | 223 | - (id)initWithData:(NSMutableData *)d 224 | startOffset:(NSUInteger)s 225 | maxLength:(NSUInteger)m 226 | timeout:(NSTimeInterval)t 227 | readLength:(NSUInteger)l 228 | terminator:(NSData *)e 229 | tag:(long)i 230 | { 231 | if((self = [super init])) 232 | { 233 | if (d) 234 | { 235 | buffer = d; 236 | startOffset = s; 237 | bufferOwner = NO; 238 | originalBufferLength = [d length]; 239 | } 240 | else 241 | { 242 | if (readLength > 0) 243 | buffer = [[NSMutableData alloc] initWithLength:readLength]; 244 | else 245 | buffer = [[NSMutableData alloc] initWithLength:0]; 246 | 247 | startOffset = 0; 248 | bufferOwner = YES; 249 | originalBufferLength = 0; 250 | } 251 | 252 | bytesDone = 0; 253 | maxLength = m; 254 | timeout = t; 255 | readLength = l; 256 | term = [e copy]; 257 | tag = i; 258 | } 259 | return self; 260 | } 261 | 262 | /** 263 | * For read packets without a set terminator, returns the safe length of data that can be read 264 | * without exceeding the maxLength, or forcing a resize of the buffer if at all possible. 265 | **/ 266 | - (NSUInteger)readLengthForNonTerm 267 | { 268 | NSAssert(term == nil, @"This method does not apply to term reads"); 269 | 270 | if (readLength > 0) 271 | { 272 | // Read a specific length of data 273 | 274 | return readLength - bytesDone; 275 | 276 | // No need to avoid resizing the buffer. 277 | // It should be resized if the buffer space is less than the requested read length. 278 | } 279 | else 280 | { 281 | // Read all available data 282 | 283 | NSUInteger result = READALL_CHUNKSIZE; 284 | 285 | if (maxLength > 0) 286 | { 287 | result = MIN(result, (maxLength - bytesDone)); 288 | } 289 | 290 | if (!bufferOwner) 291 | { 292 | // We did NOT create the buffer. 293 | // It is owned by the caller. 294 | // Avoid resizing the buffer if at all possible. 295 | 296 | if ([buffer length] == originalBufferLength) 297 | { 298 | NSUInteger buffSize = [buffer length]; 299 | NSUInteger buffSpace = buffSize - startOffset - bytesDone; 300 | 301 | if (buffSpace > 0) 302 | { 303 | result = MIN(result, buffSpace); 304 | } 305 | } 306 | } 307 | 308 | return result; 309 | } 310 | } 311 | 312 | /** 313 | * For read packets with a set terminator, returns the safe length of data that can be read 314 | * without going over a terminator, or the maxLength, or forcing a resize of the buffer if at all possible. 315 | * 316 | * It is assumed the terminator has not already been read. 317 | **/ 318 | - (NSUInteger)readLengthForTerm 319 | { 320 | NSAssert(term != nil, @"This method does not apply to non-term reads"); 321 | 322 | // What we're going to do is look for a partial sequence of the terminator at the end of the buffer. 323 | // If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term, 324 | // and we can only read that amount. 325 | // Otherwise, we're safe to read the entire length of the term. 326 | 327 | NSUInteger termLength = [term length]; 328 | 329 | // Shortcuts 330 | if (bytesDone == 0) return termLength; 331 | if (termLength == 1) return termLength; 332 | 333 | // i = index within buffer at which to check data 334 | // j = length of term to check against 335 | 336 | NSUInteger i, j; 337 | if (bytesDone >= termLength) 338 | { 339 | i = bytesDone - termLength + 1; 340 | j = termLength - 1; 341 | } 342 | else 343 | { 344 | i = 0; 345 | j = bytesDone; 346 | } 347 | 348 | NSUInteger result = termLength; 349 | 350 | void *buf = [buffer mutableBytes]; 351 | const void *termBuf = [term bytes]; 352 | 353 | while (i < bytesDone) 354 | { 355 | void *subbuf = buf + startOffset + i; 356 | 357 | if (memcmp(subbuf, termBuf, j) == 0) 358 | { 359 | result = termLength - j; 360 | break; 361 | } 362 | 363 | i++; 364 | j--; 365 | } 366 | 367 | if (maxLength > 0) 368 | { 369 | result = MIN(result, (maxLength - bytesDone)); 370 | } 371 | 372 | if (!bufferOwner) 373 | { 374 | // We did NOT create the buffer. 375 | // It is owned by the caller. 376 | // Avoid resizing the buffer if at all possible. 377 | 378 | if ([buffer length] == originalBufferLength) 379 | { 380 | NSUInteger buffSize = [buffer length]; 381 | NSUInteger buffSpace = buffSize - startOffset - bytesDone; 382 | 383 | if (buffSpace > 0) 384 | { 385 | result = MIN(result, buffSpace); 386 | } 387 | } 388 | } 389 | 390 | return result; 391 | } 392 | 393 | /** 394 | * For read packets with a set terminator, 395 | * returns the safe length of data that can be read from the given preBuffer, 396 | * without going over a terminator or the maxLength. 397 | * 398 | * It is assumed the terminator has not already been read. 399 | **/ 400 | - (NSUInteger)readLengthForTermWithPreBuffer:(NSData *)preBuffer found:(BOOL *)foundPtr 401 | { 402 | NSAssert(term != nil, @"This method does not apply to non-term reads"); 403 | NSAssert([preBuffer length] > 0, @"Invoked with empty pre buffer!"); 404 | 405 | // We know that the terminator, as a whole, doesn't exist in our own buffer. 406 | // But it is possible that a portion of it exists in our buffer. 407 | // So we're going to look for the terminator starting with a portion of our own buffer. 408 | // 409 | // Example: 410 | // 411 | // term length = 3 bytes 412 | // bytesDone = 5 bytes 413 | // preBuffer length = 5 bytes 414 | // 415 | // If we append the preBuffer to our buffer, 416 | // it would look like this: 417 | // 418 | // --------------------- 419 | // |B|B|B|B|B|P|P|P|P|P| 420 | // --------------------- 421 | // 422 | // So we start our search here: 423 | // 424 | // --------------------- 425 | // |B|B|B|B|B|P|P|P|P|P| 426 | // -------^-^-^--------- 427 | // 428 | // And move forwards... 429 | // 430 | // --------------------- 431 | // |B|B|B|B|B|P|P|P|P|P| 432 | // ---------^-^-^------- 433 | // 434 | // Until we find the terminator or reach the end. 435 | // 436 | // --------------------- 437 | // |B|B|B|B|B|P|P|P|P|P| 438 | // ---------------^-^-^- 439 | 440 | BOOL found = NO; 441 | 442 | NSUInteger termLength = [term length]; 443 | NSUInteger preBufferLength = [preBuffer length]; 444 | 445 | if ((bytesDone + preBufferLength) < termLength) 446 | { 447 | // Not enough data for a full term sequence yet 448 | return preBufferLength; 449 | } 450 | 451 | NSUInteger maxPreBufferLength; 452 | if (maxLength > 0) { 453 | maxPreBufferLength = MIN(preBufferLength, (maxLength - bytesDone)); 454 | 455 | // Note: maxLength >= termLength 456 | } 457 | else { 458 | maxPreBufferLength = preBufferLength; 459 | } 460 | 461 | Byte seq[termLength]; 462 | const void *termBuf = [term bytes]; 463 | 464 | NSUInteger bufLen = MIN(bytesDone, (termLength - 1)); 465 | void *buf = [buffer mutableBytes] + startOffset + bytesDone - bufLen; 466 | 467 | NSUInteger preLen = termLength - bufLen; 468 | void *pre = (void *)[preBuffer bytes]; 469 | 470 | NSUInteger loopCount = bufLen + maxPreBufferLength - termLength + 1; // Plus one. See example above. 471 | 472 | NSUInteger result = preBufferLength; 473 | 474 | NSUInteger i; 475 | for (i = 0; i < loopCount; i++) 476 | { 477 | if (bufLen > 0) 478 | { 479 | // Combining bytes from buffer and preBuffer 480 | 481 | memcpy(seq, buf, bufLen); 482 | memcpy(seq + bufLen, pre, preLen); 483 | 484 | if (memcmp(seq, termBuf, termLength) == 0) 485 | { 486 | result = preLen; 487 | found = YES; 488 | break; 489 | } 490 | 491 | buf++; 492 | bufLen--; 493 | preLen++; 494 | } 495 | else 496 | { 497 | // Comparing directly from preBuffer 498 | 499 | if (memcmp(pre, termBuf, termLength) == 0) 500 | { 501 | NSUInteger preOffset = pre - [preBuffer bytes]; // pointer arithmetic 502 | 503 | result = preOffset + termLength; 504 | found = YES; 505 | break; 506 | } 507 | 508 | pre++; 509 | } 510 | } 511 | 512 | // There is no need to avoid resizing the buffer in this particular situation. 513 | 514 | if (foundPtr) *foundPtr = found; 515 | return result; 516 | } 517 | 518 | /** 519 | * Assuming pre-buffering is enabled, returns the amount of data that can be read 520 | * without going over the maxLength. 521 | **/ 522 | - (NSUInteger)prebufferReadLengthForTerm 523 | { 524 | NSAssert(term != nil, @"This method does not apply to non-term reads"); 525 | 526 | NSUInteger result = READALL_CHUNKSIZE; 527 | 528 | if (maxLength > 0) 529 | { 530 | result = MIN(result, (maxLength - bytesDone)); 531 | } 532 | 533 | if (!bufferOwner) 534 | { 535 | // We did NOT create the buffer. 536 | // It is owned by the caller. 537 | // Avoid resizing the buffer if at all possible. 538 | 539 | if ([buffer length] == originalBufferLength) 540 | { 541 | NSUInteger buffSize = [buffer length]; 542 | NSUInteger buffSpace = buffSize - startOffset - bytesDone; 543 | 544 | if (buffSpace > 0) 545 | { 546 | result = MIN(result, buffSpace); 547 | } 548 | } 549 | } 550 | 551 | return result; 552 | } 553 | 554 | /** 555 | * For read packets with a set terminator, scans the packet buffer for the term. 556 | * It is assumed the terminator had not been fully read prior to the new bytes. 557 | * 558 | * If the term is found, the number of excess bytes after the term are returned. 559 | * If the term is not found, this method will return -1. 560 | * 561 | * Note: A return value of zero means the term was found at the very end. 562 | **/ 563 | - (NSInteger)searchForTermAfterPreBuffering:(NSUInteger)numBytes 564 | { 565 | NSAssert(term != nil, @"This method does not apply to non-term reads"); 566 | NSAssert(bytesDone >= numBytes, @"Invoked with invalid numBytes!"); 567 | 568 | // We try to start the search such that the first new byte read matches up with the last byte of the term. 569 | // We continue searching forward after this until the term no longer fits into the buffer. 570 | 571 | NSUInteger termLength = [term length]; 572 | const void *termBuffer = [term bytes]; 573 | 574 | // Remember: This method is called after the bytesDone variable has been updated. 575 | 576 | NSUInteger prevBytesDone = bytesDone - numBytes; 577 | 578 | NSUInteger i; 579 | if (prevBytesDone >= termLength) 580 | i = prevBytesDone - termLength + 1; 581 | else 582 | i = 0; 583 | 584 | while ((i + termLength) <= bytesDone) 585 | { 586 | void *subBuffer = [buffer mutableBytes] + startOffset + i; 587 | 588 | if(memcmp(subBuffer, termBuffer, termLength) == 0) 589 | { 590 | return bytesDone - (i + termLength); 591 | } 592 | 593 | i++; 594 | } 595 | 596 | return -1; 597 | } 598 | 599 | 600 | @end 601 | 602 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 603 | #pragma mark - 604 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 605 | 606 | /** 607 | * The AsyncWritePacket encompasses the instructions for any given write. 608 | **/ 609 | @interface AsyncWritePacket : NSObject 610 | { 611 | @public 612 | NSData *buffer; 613 | NSUInteger bytesDone; 614 | long tag; 615 | NSTimeInterval timeout; 616 | } 617 | - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; 618 | @end 619 | 620 | @implementation AsyncWritePacket 621 | 622 | - (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i 623 | { 624 | if((self = [super init])) 625 | { 626 | buffer = d; 627 | timeout = t; 628 | tag = i; 629 | bytesDone = 0; 630 | } 631 | return self; 632 | } 633 | 634 | 635 | @end 636 | 637 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 638 | #pragma mark - 639 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 640 | 641 | /** 642 | * The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. 643 | * This class my be altered to support more than just TLS in the future. 644 | **/ 645 | @interface AsyncSpecialPacket : NSObject 646 | { 647 | @public 648 | NSDictionary *tlsSettings; 649 | } 650 | - (id)initWithTLSSettings:(NSDictionary *)settings; 651 | @end 652 | 653 | @implementation AsyncSpecialPacket 654 | 655 | - (id)initWithTLSSettings:(NSDictionary *)settings 656 | { 657 | if((self = [super init])) 658 | { 659 | tlsSettings = [settings copy]; 660 | } 661 | return self; 662 | } 663 | 664 | 665 | @end 666 | 667 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 668 | #pragma mark - 669 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 670 | 671 | @implementation AsyncSocket 672 | 673 | - (id)init 674 | { 675 | return [self initWithDelegate:nil userData:0]; 676 | } 677 | 678 | - (id)initWithDelegate:(id)delegate 679 | { 680 | return [self initWithDelegate:delegate userData:0]; 681 | } 682 | 683 | // Designated initializer. 684 | - (id)initWithDelegate:(id)delegate userData:(long)userData 685 | { 686 | if((self = [super init])) 687 | { 688 | theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0; 689 | theDelegate = delegate; 690 | theUserData = userData; 691 | 692 | theNativeSocket4 = 0; 693 | theNativeSocket6 = 0; 694 | 695 | theSocket4 = NULL; 696 | theSource4 = NULL; 697 | 698 | theSocket6 = NULL; 699 | theSource6 = NULL; 700 | 701 | theRunLoop = NULL; 702 | theReadStream = NULL; 703 | theWriteStream = NULL; 704 | 705 | theConnectTimer = nil; 706 | 707 | theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; 708 | theCurrentRead = nil; 709 | theReadTimer = nil; 710 | 711 | partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE]; 712 | 713 | theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; 714 | theCurrentWrite = nil; 715 | theWriteTimer = nil; 716 | 717 | // Socket context 718 | NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext"); 719 | theContext.version = 0; 720 | theContext.info = (__bridge void *)(self); 721 | theContext.retain = nil; 722 | theContext.release = nil; 723 | theContext.copyDescription = nil; 724 | 725 | // Default run loop modes 726 | theRunLoopModes = [NSArray arrayWithObject:NSDefaultRunLoopMode]; 727 | } 728 | return self; 729 | } 730 | 731 | // The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. 732 | - (void)dealloc 733 | { 734 | [self close]; 735 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 736 | } 737 | 738 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 739 | #pragma mark Thread-Safety 740 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 741 | 742 | - (void)checkForThreadSafety 743 | { 744 | if (theRunLoop && (theRunLoop != CFRunLoopGetCurrent())) 745 | { 746 | // AsyncSocket is RunLoop based. 747 | // It is designed to be run and accessed from a particular thread/runloop. 748 | // As such, it is faster as it does not have the overhead of locks/synchronization. 749 | // 750 | // However, this places a minimal requirement on the developer to maintain thread-safety. 751 | // If you are seeing errors or crashes in AsyncSocket, 752 | // it is very likely that thread-safety has been broken. 753 | // This method may be enabled via the DEBUG_THREAD_SAFETY macro, 754 | // and will allow you to discover the place in your code where thread-safety is being broken. 755 | // 756 | // Note: 757 | // 758 | // If you find you constantly need to access your socket from various threads, 759 | // you may prefer to use GCDAsyncSocket which is thread-safe. 760 | 761 | [NSException raise:AsyncSocketException 762 | format:@"Attempting to access AsyncSocket instance from incorrect thread."]; 763 | } 764 | } 765 | 766 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 767 | #pragma mark Accessors 768 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 769 | 770 | - (long)userData 771 | { 772 | #if DEBUG_THREAD_SAFETY 773 | [self checkForThreadSafety]; 774 | #endif 775 | 776 | return theUserData; 777 | } 778 | 779 | - (void)setUserData:(long)userData 780 | { 781 | #if DEBUG_THREAD_SAFETY 782 | [self checkForThreadSafety]; 783 | #endif 784 | 785 | theUserData = userData; 786 | } 787 | 788 | - (id)delegate 789 | { 790 | #if DEBUG_THREAD_SAFETY 791 | [self checkForThreadSafety]; 792 | #endif 793 | 794 | return theDelegate; 795 | } 796 | 797 | - (void)setDelegate:(id)delegate 798 | { 799 | #if DEBUG_THREAD_SAFETY 800 | [self checkForThreadSafety]; 801 | #endif 802 | 803 | theDelegate = delegate; 804 | } 805 | 806 | - (BOOL)canSafelySetDelegate 807 | { 808 | #if DEBUG_THREAD_SAFETY 809 | [self checkForThreadSafety]; 810 | #endif 811 | 812 | return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); 813 | } 814 | 815 | - (CFSocketRef)getCFSocket 816 | { 817 | #if DEBUG_THREAD_SAFETY 818 | [self checkForThreadSafety]; 819 | #endif 820 | 821 | if(theSocket4) 822 | return theSocket4; 823 | else 824 | return theSocket6; 825 | } 826 | 827 | - (CFReadStreamRef)getCFReadStream 828 | { 829 | #if DEBUG_THREAD_SAFETY 830 | [self checkForThreadSafety]; 831 | #endif 832 | 833 | return theReadStream; 834 | } 835 | 836 | - (CFWriteStreamRef)getCFWriteStream 837 | { 838 | #if DEBUG_THREAD_SAFETY 839 | [self checkForThreadSafety]; 840 | #endif 841 | 842 | return theWriteStream; 843 | } 844 | 845 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 846 | #pragma mark Progress 847 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 848 | 849 | - (float)progressOfReadReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total 850 | { 851 | #if DEBUG_THREAD_SAFETY 852 | [self checkForThreadSafety]; 853 | #endif 854 | 855 | // Check to make sure we're actually reading something right now, 856 | // and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS). 857 | if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]]) 858 | { 859 | if (tag != NULL) *tag = 0; 860 | if (done != NULL) *done = 0; 861 | if (total != NULL) *total = 0; 862 | 863 | return NAN; 864 | } 865 | 866 | // It's only possible to know the progress of our read if we're reading to a certain length. 867 | // If we're reading to data, we of course have no idea when the data will arrive. 868 | // If we're reading to timeout, then we have no idea when the next chunk of data will arrive. 869 | 870 | NSUInteger d = theCurrentRead->bytesDone; 871 | NSUInteger t = theCurrentRead->readLength; 872 | 873 | if (tag != NULL) *tag = theCurrentRead->tag; 874 | if (done != NULL) *done = d; 875 | if (total != NULL) *total = t; 876 | 877 | if (t > 0.0) 878 | return (float)d / (float)t; 879 | else 880 | return 1.0F; 881 | } 882 | 883 | - (float)progressOfWriteReturningTag:(long *)tag bytesDone:(NSUInteger *)done total:(NSUInteger *)total 884 | { 885 | #if DEBUG_THREAD_SAFETY 886 | [self checkForThreadSafety]; 887 | #endif 888 | 889 | // Check to make sure we're actually writing something right now, 890 | // and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS). 891 | if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]]) 892 | { 893 | if (tag != NULL) *tag = 0; 894 | if (done != NULL) *done = 0; 895 | if (total != NULL) *total = 0; 896 | 897 | return NAN; 898 | } 899 | 900 | NSUInteger d = theCurrentWrite->bytesDone; 901 | NSUInteger t = [theCurrentWrite->buffer length]; 902 | 903 | if (tag != NULL) *tag = theCurrentWrite->tag; 904 | if (done != NULL) *done = d; 905 | if (total != NULL) *total = t; 906 | 907 | return (float)d / (float)t; 908 | } 909 | 910 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 911 | #pragma mark Run Loop 912 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 913 | 914 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source 915 | { 916 | for (NSString *runLoopMode in theRunLoopModes) 917 | { 918 | CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); 919 | } 920 | } 921 | 922 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source 923 | { 924 | for (NSString *runLoopMode in theRunLoopModes) 925 | { 926 | CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); 927 | } 928 | } 929 | 930 | - (void)runLoopAddSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode 931 | { 932 | CFRunLoopAddSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); 933 | } 934 | 935 | - (void)runLoopRemoveSource:(CFRunLoopSourceRef)source mode:(NSString *)runLoopMode 936 | { 937 | CFRunLoopRemoveSource(theRunLoop, source, (__bridge CFStringRef)runLoopMode); 938 | } 939 | 940 | - (void)runLoopAddTimer:(NSTimer *)timer 941 | { 942 | for (NSString *runLoopMode in theRunLoopModes) 943 | { 944 | CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); 945 | } 946 | } 947 | 948 | - (void)runLoopRemoveTimer:(NSTimer *)timer 949 | { 950 | for (NSString *runLoopMode in theRunLoopModes) 951 | { 952 | CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); 953 | } 954 | } 955 | 956 | - (void)runLoopAddTimer:(NSTimer *)timer mode:(NSString *)runLoopMode 957 | { 958 | CFRunLoopAddTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); 959 | } 960 | 961 | - (void)runLoopRemoveTimer:(NSTimer *)timer mode:(NSString *)runLoopMode 962 | { 963 | CFRunLoopRemoveTimer(theRunLoop, (__bridge CFRunLoopTimerRef)timer, (__bridge CFStringRef)runLoopMode); 964 | } 965 | 966 | - (void)runLoopUnscheduleReadStream 967 | { 968 | for (NSString *runLoopMode in theRunLoopModes) 969 | { 970 | CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, (__bridge CFStringRef)runLoopMode); 971 | } 972 | CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); 973 | } 974 | 975 | - (void)runLoopUnscheduleWriteStream 976 | { 977 | for (NSString *runLoopMode in theRunLoopModes) 978 | { 979 | CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, (__bridge CFStringRef)runLoopMode); 980 | } 981 | CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); 982 | } 983 | 984 | 985 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 986 | #pragma mark Configuration 987 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 988 | 989 | /** 990 | * See the header file for a full explanation of pre-buffering. 991 | **/ 992 | - (void)enablePreBuffering 993 | { 994 | #if DEBUG_THREAD_SAFETY 995 | [self checkForThreadSafety]; 996 | #endif 997 | 998 | theFlags |= kEnablePreBuffering; 999 | } 1000 | 1001 | /** 1002 | * See the header file for a full explanation of this method. 1003 | **/ 1004 | - (BOOL)moveToRunLoop:(NSRunLoop *)runLoop 1005 | { 1006 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 1007 | @"moveToRunLoop must be called from within the current RunLoop!"); 1008 | 1009 | if(runLoop == nil) 1010 | { 1011 | return NO; 1012 | } 1013 | if(theRunLoop == [runLoop getCFRunLoop]) 1014 | { 1015 | return YES; 1016 | } 1017 | 1018 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 1019 | theFlags &= ~kDequeueReadScheduled; 1020 | theFlags &= ~kDequeueWriteScheduled; 1021 | 1022 | if(theReadStream && theWriteStream) 1023 | { 1024 | [self runLoopUnscheduleReadStream]; 1025 | [self runLoopUnscheduleWriteStream]; 1026 | } 1027 | 1028 | if(theSource4) [self runLoopRemoveSource:theSource4]; 1029 | if(theSource6) [self runLoopRemoveSource:theSource6]; 1030 | 1031 | if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; 1032 | if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; 1033 | 1034 | theRunLoop = [runLoop getCFRunLoop]; 1035 | 1036 | if(theReadTimer) [self runLoopAddTimer:theReadTimer]; 1037 | if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; 1038 | 1039 | if(theSource4) [self runLoopAddSource:theSource4]; 1040 | if(theSource6) [self runLoopAddSource:theSource6]; 1041 | 1042 | if(theReadStream && theWriteStream) 1043 | { 1044 | if(![self attachStreamsToRunLoop:runLoop error:nil]) 1045 | { 1046 | return NO; 1047 | } 1048 | } 1049 | 1050 | [runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes]; 1051 | [runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes]; 1052 | [runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes]; 1053 | 1054 | return YES; 1055 | } 1056 | 1057 | /** 1058 | * See the header file for a full explanation of this method. 1059 | **/ 1060 | - (BOOL)setRunLoopModes:(NSArray *)runLoopModes 1061 | { 1062 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 1063 | @"setRunLoopModes must be called from within the current RunLoop!"); 1064 | 1065 | if([runLoopModes count] == 0) 1066 | { 1067 | return NO; 1068 | } 1069 | if([theRunLoopModes isEqualToArray:runLoopModes]) 1070 | { 1071 | return YES; 1072 | } 1073 | 1074 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 1075 | theFlags &= ~kDequeueReadScheduled; 1076 | theFlags &= ~kDequeueWriteScheduled; 1077 | 1078 | if(theReadStream && theWriteStream) 1079 | { 1080 | [self runLoopUnscheduleReadStream]; 1081 | [self runLoopUnscheduleWriteStream]; 1082 | } 1083 | 1084 | if(theSource4) [self runLoopRemoveSource:theSource4]; 1085 | if(theSource6) [self runLoopRemoveSource:theSource6]; 1086 | 1087 | if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; 1088 | if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; 1089 | 1090 | theRunLoopModes = [runLoopModes copy]; 1091 | 1092 | if(theReadTimer) [self runLoopAddTimer:theReadTimer]; 1093 | if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; 1094 | 1095 | if(theSource4) [self runLoopAddSource:theSource4]; 1096 | if(theSource6) [self runLoopAddSource:theSource6]; 1097 | 1098 | if(theReadStream && theWriteStream) 1099 | { 1100 | // Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop. 1101 | // So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter. 1102 | // Instead we pass nil, which will result in the method properly using the current run loop. 1103 | 1104 | if(![self attachStreamsToRunLoop:nil error:nil]) 1105 | { 1106 | return NO; 1107 | } 1108 | } 1109 | 1110 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1111 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1112 | [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1113 | 1114 | return YES; 1115 | } 1116 | 1117 | - (BOOL)addRunLoopMode:(NSString *)runLoopMode 1118 | { 1119 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 1120 | @"addRunLoopMode must be called from within the current RunLoop!"); 1121 | 1122 | if(runLoopMode == nil) 1123 | { 1124 | return NO; 1125 | } 1126 | if([theRunLoopModes containsObject:runLoopMode]) 1127 | { 1128 | return YES; 1129 | } 1130 | 1131 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 1132 | theFlags &= ~kDequeueReadScheduled; 1133 | theFlags &= ~kDequeueWriteScheduled; 1134 | 1135 | NSArray *newRunLoopModes = [theRunLoopModes arrayByAddingObject:runLoopMode]; 1136 | theRunLoopModes = newRunLoopModes; 1137 | 1138 | if(theReadTimer) [self runLoopAddTimer:theReadTimer mode:runLoopMode]; 1139 | if(theWriteTimer) [self runLoopAddTimer:theWriteTimer mode:runLoopMode]; 1140 | 1141 | if(theSource4) [self runLoopAddSource:theSource4 mode:runLoopMode]; 1142 | if(theSource6) [self runLoopAddSource:theSource6 mode:runLoopMode]; 1143 | 1144 | if(theReadStream && theWriteStream) 1145 | { 1146 | CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); 1147 | CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); 1148 | } 1149 | 1150 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1151 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1152 | [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1153 | 1154 | return YES; 1155 | } 1156 | 1157 | - (BOOL)removeRunLoopMode:(NSString *)runLoopMode 1158 | { 1159 | NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), 1160 | @"addRunLoopMode must be called from within the current RunLoop!"); 1161 | 1162 | if(runLoopMode == nil) 1163 | { 1164 | return NO; 1165 | } 1166 | if(![theRunLoopModes containsObject:runLoopMode]) 1167 | { 1168 | return YES; 1169 | } 1170 | 1171 | NSMutableArray *newRunLoopModes = [theRunLoopModes mutableCopy]; 1172 | [newRunLoopModes removeObject:runLoopMode]; 1173 | 1174 | if([newRunLoopModes count] == 0) 1175 | { 1176 | return NO; 1177 | } 1178 | 1179 | [NSObject cancelPreviousPerformRequestsWithTarget:self]; 1180 | theFlags &= ~kDequeueReadScheduled; 1181 | theFlags &= ~kDequeueWriteScheduled; 1182 | 1183 | theRunLoopModes = [newRunLoopModes copy]; 1184 | 1185 | if(theReadTimer) [self runLoopRemoveTimer:theReadTimer mode:runLoopMode]; 1186 | if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer mode:runLoopMode]; 1187 | 1188 | if(theSource4) [self runLoopRemoveSource:theSource4 mode:runLoopMode]; 1189 | if(theSource6) [self runLoopRemoveSource:theSource6 mode:runLoopMode]; 1190 | 1191 | if(theReadStream && theWriteStream) 1192 | { 1193 | CFReadStreamScheduleWithRunLoop(theReadStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); 1194 | CFWriteStreamScheduleWithRunLoop(theWriteStream, CFRunLoopGetCurrent(), (__bridge CFStringRef)runLoopMode); 1195 | } 1196 | 1197 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1198 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1199 | [self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 1200 | 1201 | return YES; 1202 | } 1203 | 1204 | - (NSArray *)runLoopModes 1205 | { 1206 | #if DEBUG_THREAD_SAFETY 1207 | [self checkForThreadSafety]; 1208 | #endif 1209 | 1210 | return theRunLoopModes; 1211 | } 1212 | 1213 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1214 | #pragma mark Accepting 1215 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1216 | 1217 | - (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr 1218 | { 1219 | return [self acceptOnInterface:nil port:port error:errPtr]; 1220 | } 1221 | 1222 | /** 1223 | * To accept on a certain interface, pass the address to accept on. 1224 | * To accept on any interface, pass nil or an empty string. 1225 | * To accept only connections from localhost pass "localhost" or "loopback". 1226 | **/ 1227 | - (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr 1228 | { 1229 | if (theDelegate == NULL) 1230 | { 1231 | [NSException raise:AsyncSocketException 1232 | format:@"Attempting to accept without a delegate. Set a delegate first."]; 1233 | } 1234 | 1235 | if (![self isDisconnected]) 1236 | { 1237 | [NSException raise:AsyncSocketException 1238 | format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; 1239 | } 1240 | 1241 | // Clear queues (spurious read/write requests post disconnect) 1242 | [self emptyQueues]; 1243 | 1244 | // Set up the listen sockaddr structs if needed. 1245 | 1246 | NSData *address4 = nil, *address6 = nil; 1247 | if(interface == nil || ([interface length] == 0)) 1248 | { 1249 | // Accept on ANY address 1250 | struct sockaddr_in nativeAddr4; 1251 | nativeAddr4.sin_len = sizeof(struct sockaddr_in); 1252 | nativeAddr4.sin_family = AF_INET; 1253 | nativeAddr4.sin_port = htons(port); 1254 | nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY); 1255 | memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); 1256 | 1257 | struct sockaddr_in6 nativeAddr6; 1258 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); 1259 | nativeAddr6.sin6_family = AF_INET6; 1260 | nativeAddr6.sin6_port = htons(port); 1261 | nativeAddr6.sin6_flowinfo = 0; 1262 | nativeAddr6.sin6_addr = in6addr_any; 1263 | nativeAddr6.sin6_scope_id = 0; 1264 | 1265 | // Wrap the native address structures for CFSocketSetAddress. 1266 | address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; 1267 | address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; 1268 | } 1269 | else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) 1270 | { 1271 | // Accept only on LOOPBACK address 1272 | struct sockaddr_in nativeAddr4; 1273 | nativeAddr4.sin_len = sizeof(struct sockaddr_in); 1274 | nativeAddr4.sin_family = AF_INET; 1275 | nativeAddr4.sin_port = htons(port); 1276 | nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 1277 | memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); 1278 | 1279 | struct sockaddr_in6 nativeAddr6; 1280 | nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); 1281 | nativeAddr6.sin6_family = AF_INET6; 1282 | nativeAddr6.sin6_port = htons(port); 1283 | nativeAddr6.sin6_flowinfo = 0; 1284 | nativeAddr6.sin6_addr = in6addr_loopback; 1285 | nativeAddr6.sin6_scope_id = 0; 1286 | 1287 | // Wrap the native address structures for CFSocketSetAddress. 1288 | address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; 1289 | address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; 1290 | } 1291 | else 1292 | { 1293 | NSString *portStr = [NSString stringWithFormat:@"%hu", port]; 1294 | 1295 | struct addrinfo hints, *res, *res0; 1296 | 1297 | memset(&hints, 0, sizeof(hints)); 1298 | hints.ai_family = PF_UNSPEC; 1299 | hints.ai_socktype = SOCK_STREAM; 1300 | hints.ai_protocol = IPPROTO_TCP; 1301 | hints.ai_flags = AI_PASSIVE; 1302 | 1303 | int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0); 1304 | 1305 | if (error) 1306 | { 1307 | if (errPtr) 1308 | { 1309 | NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; 1310 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1311 | 1312 | *errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; 1313 | } 1314 | } 1315 | else 1316 | { 1317 | for (res = res0; res; res = res->ai_next) 1318 | { 1319 | if (!address4 && (res->ai_family == AF_INET)) 1320 | { 1321 | // Found IPv4 address 1322 | // Wrap the native address structures for CFSocketSetAddress. 1323 | address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 1324 | } 1325 | else if (!address6 && (res->ai_family == AF_INET6)) 1326 | { 1327 | // Found IPv6 address 1328 | // Wrap the native address structures for CFSocketSetAddress. 1329 | address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; 1330 | } 1331 | } 1332 | freeaddrinfo(res0); 1333 | } 1334 | 1335 | if(!address4 && !address6) return NO; 1336 | } 1337 | 1338 | // Create the sockets. 1339 | 1340 | if (address4) 1341 | { 1342 | theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr]; 1343 | if (theSocket4 == NULL) goto Failed; 1344 | } 1345 | 1346 | if (address6) 1347 | { 1348 | theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr]; 1349 | 1350 | // Note: The iPhone doesn't currently support IPv6 1351 | 1352 | #if !TARGET_OS_IPHONE 1353 | if (theSocket6 == NULL) goto Failed; 1354 | #endif 1355 | } 1356 | 1357 | // Attach the sockets to the run loop so that callback methods work 1358 | 1359 | [self attachSocketsToRunLoop:nil error:nil]; 1360 | 1361 | // Set the SO_REUSEADDR flags. 1362 | 1363 | int reuseOn = 1; 1364 | if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); 1365 | if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); 1366 | 1367 | // Set the local bindings which causes the sockets to start listening. 1368 | 1369 | CFSocketError err; 1370 | if (theSocket4) 1371 | { 1372 | err = CFSocketSetAddress(theSocket4, (__bridge CFDataRef)address4); 1373 | if (err != kCFSocketSuccess) goto Failed; 1374 | 1375 | //NSLog(@"theSocket4: %hu", [self localPortFromCFSocket4:theSocket4]); 1376 | } 1377 | 1378 | if(port == 0 && theSocket4 && theSocket6) 1379 | { 1380 | // The user has passed in port 0, which means he wants to allow the kernel to choose the port for them 1381 | // However, the kernel will choose a different port for both theSocket4 and theSocket6 1382 | // So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6 1383 | UInt16 chosenPort = [self localPortFromCFSocket4:theSocket4]; 1384 | 1385 | struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; 1386 | if (pSockAddr6) // If statement to quiet the static analyzer 1387 | { 1388 | pSockAddr6->sin6_port = htons(chosenPort); 1389 | } 1390 | } 1391 | 1392 | if (theSocket6) 1393 | { 1394 | err = CFSocketSetAddress(theSocket6, (__bridge CFDataRef)address6); 1395 | if (err != kCFSocketSuccess) goto Failed; 1396 | 1397 | //NSLog(@"theSocket6: %hu", [self localPortFromCFSocket6:theSocket6]); 1398 | } 1399 | 1400 | theFlags |= kDidStartDelegate; 1401 | return YES; 1402 | 1403 | Failed: 1404 | if(errPtr) *errPtr = [self getSocketError]; 1405 | if(theSocket4 != NULL) 1406 | { 1407 | CFSocketInvalidate(theSocket4); 1408 | CFRelease(theSocket4); 1409 | theSocket4 = NULL; 1410 | } 1411 | if(theSocket6 != NULL) 1412 | { 1413 | CFSocketInvalidate(theSocket6); 1414 | CFRelease(theSocket6); 1415 | theSocket6 = NULL; 1416 | } 1417 | return NO; 1418 | } 1419 | 1420 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1421 | #pragma mark Connecting 1422 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1423 | 1424 | - (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr 1425 | { 1426 | return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr]; 1427 | } 1428 | 1429 | /** 1430 | * This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. 1431 | * The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. 1432 | * 1433 | * Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection, 1434 | * specifically in the onSocketWillConnect: method. 1435 | **/ 1436 | - (BOOL)connectToHost:(NSString *)hostname 1437 | onPort:(UInt16)port 1438 | withTimeout:(NSTimeInterval)timeout 1439 | error:(NSError **)errPtr 1440 | { 1441 | if (theDelegate == NULL) 1442 | { 1443 | [NSException raise:AsyncSocketException 1444 | format:@"Attempting to connect without a delegate. Set a delegate first."]; 1445 | } 1446 | 1447 | if (![self isDisconnected]) 1448 | { 1449 | [NSException raise:AsyncSocketException 1450 | format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; 1451 | } 1452 | 1453 | // Clear queues (spurious read/write requests post disconnect) 1454 | [self emptyQueues]; 1455 | 1456 | if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed; 1457 | if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed; 1458 | if(![self configureStreamsAndReturnError:errPtr]) goto Failed; 1459 | if(![self openStreamsAndReturnError:errPtr]) goto Failed; 1460 | 1461 | [self startConnectTimeout:timeout]; 1462 | theFlags |= kDidStartDelegate; 1463 | 1464 | return YES; 1465 | 1466 | Failed: 1467 | [self close]; 1468 | return NO; 1469 | } 1470 | 1471 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr 1472 | { 1473 | return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:-1 error:errPtr]; 1474 | } 1475 | 1476 | /** 1477 | * This method creates an initial CFSocket to the given address. 1478 | * The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be 1479 | * created from the low-level sockets after the connection succeeds. 1480 | * 1481 | * Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection, 1482 | * specifically in the onSocketWillConnect: method. 1483 | * 1484 | * Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from 1485 | * NSNetService addresses method. 1486 | * If you have an existing struct sockaddr you can convert it to an NSData object like so: 1487 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; 1488 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; 1489 | **/ 1490 | - (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr 1491 | { 1492 | return [self connectToAddress:remoteAddr viaInterfaceAddress:nil withTimeout:timeout error:errPtr]; 1493 | } 1494 | 1495 | /** 1496 | * This method is similar to the one above, but allows you to specify which socket interface 1497 | * the connection should run over. E.g. ethernet, wifi, bluetooth, etc. 1498 | **/ 1499 | - (BOOL)connectToAddress:(NSData *)remoteAddr 1500 | viaInterfaceAddress:(NSData *)interfaceAddr 1501 | withTimeout:(NSTimeInterval)timeout 1502 | error:(NSError **)errPtr 1503 | { 1504 | if (theDelegate == NULL) 1505 | { 1506 | [NSException raise:AsyncSocketException 1507 | format:@"Attempting to connect without a delegate. Set a delegate first."]; 1508 | } 1509 | 1510 | if (![self isDisconnected]) 1511 | { 1512 | [NSException raise:AsyncSocketException 1513 | format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; 1514 | } 1515 | 1516 | // Clear queues (spurious read/write requests post disconnect) 1517 | [self emptyQueues]; 1518 | 1519 | if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed; 1520 | if(![self bindSocketToAddress:interfaceAddr error:errPtr]) goto Failed; 1521 | if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed; 1522 | if(![self configureSocketAndReturnError:errPtr]) goto Failed; 1523 | if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed; 1524 | 1525 | [self startConnectTimeout:timeout]; 1526 | theFlags |= kDidStartDelegate; 1527 | 1528 | return YES; 1529 | 1530 | Failed: 1531 | [self close]; 1532 | return NO; 1533 | } 1534 | 1535 | - (void)startConnectTimeout:(NSTimeInterval)timeout 1536 | { 1537 | if(timeout >= 0.0) 1538 | { 1539 | theConnectTimer = [NSTimer timerWithTimeInterval:timeout 1540 | target:self 1541 | selector:@selector(doConnectTimeout:) 1542 | userInfo:nil 1543 | repeats:NO]; 1544 | [self runLoopAddTimer:theConnectTimer]; 1545 | } 1546 | } 1547 | 1548 | - (void)endConnectTimeout 1549 | { 1550 | [theConnectTimer invalidate]; 1551 | theConnectTimer = nil; 1552 | } 1553 | 1554 | - (void)doConnectTimeout:(NSTimer *)timer 1555 | { 1556 | #pragma unused(timer) 1557 | 1558 | [self endConnectTimeout]; 1559 | [self closeWithError:[self getConnectTimeoutError]]; 1560 | } 1561 | 1562 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1563 | #pragma mark Socket Implementation 1564 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1565 | 1566 | /** 1567 | * Creates the accept sockets. 1568 | * Returns true if either IPv4 or IPv6 is created. 1569 | * If either is missing, an error is returned (even though the method may return true). 1570 | **/ 1571 | - (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr 1572 | { 1573 | struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; 1574 | int addressFamily = pSockAddr->sa_family; 1575 | 1576 | CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault, 1577 | addressFamily, 1578 | SOCK_STREAM, 1579 | 0, 1580 | kCFSocketAcceptCallBack, // Callback flags 1581 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method 1582 | &theContext); 1583 | 1584 | if(theSocket == NULL) 1585 | { 1586 | if(errPtr) *errPtr = [self getSocketError]; 1587 | } 1588 | 1589 | return theSocket; 1590 | } 1591 | 1592 | - (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr 1593 | { 1594 | struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes]; 1595 | 1596 | if(pSockAddr->sa_family == AF_INET) 1597 | { 1598 | theSocket4 = CFSocketCreate(NULL, // Default allocator 1599 | PF_INET, // Protocol Family 1600 | SOCK_STREAM, // Socket Type 1601 | IPPROTO_TCP, // Protocol 1602 | kCFSocketConnectCallBack, // Callback flags 1603 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method 1604 | &theContext); // Socket Context 1605 | 1606 | if(theSocket4 == NULL) 1607 | { 1608 | if (errPtr) *errPtr = [self getSocketError]; 1609 | return NO; 1610 | } 1611 | } 1612 | else if(pSockAddr->sa_family == AF_INET6) 1613 | { 1614 | theSocket6 = CFSocketCreate(NULL, // Default allocator 1615 | PF_INET6, // Protocol Family 1616 | SOCK_STREAM, // Socket Type 1617 | IPPROTO_TCP, // Protocol 1618 | kCFSocketConnectCallBack, // Callback flags 1619 | (CFSocketCallBack)&MyCFSocketCallback, // Callback method 1620 | &theContext); // Socket Context 1621 | 1622 | if(theSocket6 == NULL) 1623 | { 1624 | if (errPtr) *errPtr = [self getSocketError]; 1625 | return NO; 1626 | } 1627 | } 1628 | else 1629 | { 1630 | if (errPtr) 1631 | { 1632 | NSString *errMsg = @"Remote address is not IPv4 or IPv6"; 1633 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1634 | 1635 | *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; 1636 | } 1637 | return NO; 1638 | } 1639 | 1640 | return YES; 1641 | } 1642 | 1643 | - (BOOL)bindSocketToAddress:(NSData *)interfaceAddr error:(NSError **)errPtr 1644 | { 1645 | if (interfaceAddr == nil) return YES; 1646 | 1647 | struct sockaddr *pSockAddr = (struct sockaddr *)[interfaceAddr bytes]; 1648 | 1649 | CFSocketRef theSocket = (theSocket4 != NULL) ? theSocket4 : theSocket6; 1650 | NSAssert((theSocket != NULL), @"bindSocketToAddress called without valid socket"); 1651 | 1652 | CFSocketNativeHandle nativeSocket = CFSocketGetNative(theSocket); 1653 | 1654 | if (pSockAddr->sa_family == AF_INET || pSockAddr->sa_family == AF_INET6) 1655 | { 1656 | int result = bind(nativeSocket, pSockAddr, (socklen_t)[interfaceAddr length]); 1657 | if (result != 0) 1658 | { 1659 | if (errPtr) *errPtr = [self getErrnoError]; 1660 | return NO; 1661 | } 1662 | } 1663 | else 1664 | { 1665 | if (errPtr) 1666 | { 1667 | NSString *errMsg = @"Interface address is not IPv4 or IPv6"; 1668 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 1669 | 1670 | *errPtr = [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; 1671 | } 1672 | return NO; 1673 | } 1674 | 1675 | return YES; 1676 | } 1677 | 1678 | /** 1679 | * Adds the CFSocket's to the run-loop so that callbacks will work properly. 1680 | **/ 1681 | - (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr 1682 | { 1683 | #pragma unused(errPtr) 1684 | 1685 | // Get the CFRunLoop to which the socket should be attached. 1686 | theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; 1687 | 1688 | if(theSocket4) 1689 | { 1690 | theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0); 1691 | [self runLoopAddSource:theSource4]; 1692 | } 1693 | 1694 | if(theSocket6) 1695 | { 1696 | theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); 1697 | [self runLoopAddSource:theSource6]; 1698 | } 1699 | 1700 | return YES; 1701 | } 1702 | 1703 | /** 1704 | * Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect. 1705 | * Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened. 1706 | **/ 1707 | - (BOOL)configureSocketAndReturnError:(NSError **)errPtr 1708 | { 1709 | // Call the delegate method for further configuration. 1710 | if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) 1711 | { 1712 | if([theDelegate onSocketWillConnect:self] == NO) 1713 | { 1714 | if (errPtr) *errPtr = [self getAbortError]; 1715 | return NO; 1716 | } 1717 | } 1718 | return YES; 1719 | } 1720 | 1721 | - (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr 1722 | { 1723 | // Start connecting to the given address in the background 1724 | // The MyCFSocketCallback method will be called when the connection succeeds or fails 1725 | if(theSocket4) 1726 | { 1727 | CFSocketError err = CFSocketConnectToAddress(theSocket4, (__bridge CFDataRef)remoteAddr, -1); 1728 | if(err != kCFSocketSuccess) 1729 | { 1730 | if (errPtr) *errPtr = [self getSocketError]; 1731 | return NO; 1732 | } 1733 | } 1734 | else if(theSocket6) 1735 | { 1736 | CFSocketError err = CFSocketConnectToAddress(theSocket6, (__bridge CFDataRef)remoteAddr, -1); 1737 | if(err != kCFSocketSuccess) 1738 | { 1739 | if (errPtr) *errPtr = [self getSocketError]; 1740 | return NO; 1741 | } 1742 | } 1743 | 1744 | return YES; 1745 | } 1746 | 1747 | /** 1748 | * Attempt to make the new socket. 1749 | * If an error occurs, ignore this event. 1750 | **/ 1751 | - (void)doAcceptFromSocket:(CFSocketRef)parentSocket withNewNativeSocket:(CFSocketNativeHandle)newNativeSocket 1752 | { 1753 | if(newNativeSocket) 1754 | { 1755 | // New socket inherits same delegate and run loop modes. 1756 | // Note: We use [self class] to support subclassing AsyncSocket. 1757 | AsyncSocket *newSocket = [[[self class] alloc] initWithDelegate:theDelegate]; 1758 | [newSocket setRunLoopModes:theRunLoopModes]; 1759 | 1760 | if (![newSocket createStreamsFromNative:newNativeSocket error:nil]) 1761 | { 1762 | [newSocket close]; 1763 | return; 1764 | } 1765 | 1766 | if (parentSocket == theSocket4) 1767 | newSocket->theNativeSocket4 = newNativeSocket; 1768 | else 1769 | newSocket->theNativeSocket6 = newNativeSocket; 1770 | 1771 | if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) 1772 | [theDelegate onSocket:self didAcceptNewSocket:newSocket]; 1773 | 1774 | newSocket->theFlags |= kDidStartDelegate; 1775 | 1776 | NSRunLoop *runLoop = nil; 1777 | if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) 1778 | { 1779 | runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; 1780 | } 1781 | 1782 | if(![newSocket attachStreamsToRunLoop:runLoop error:nil]) goto Failed; 1783 | if(![newSocket configureStreamsAndReturnError:nil]) goto Failed; 1784 | if(![newSocket openStreamsAndReturnError:nil]) goto Failed; 1785 | 1786 | return; 1787 | 1788 | Failed: 1789 | [newSocket close]; 1790 | } 1791 | } 1792 | 1793 | /** 1794 | * This method is called as a result of connectToAddress:withTimeout:error:. 1795 | * At this point we have an open CFSocket from which we need to create our read and write stream. 1796 | **/ 1797 | - (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError 1798 | { 1799 | NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); 1800 | 1801 | if(socketError == kCFSocketTimeout || socketError == kCFSocketError) 1802 | { 1803 | [self closeWithError:[self getSocketError]]; 1804 | return; 1805 | } 1806 | 1807 | // Get the underlying native (BSD) socket 1808 | CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock); 1809 | 1810 | // Store a reference to it 1811 | if (sock == theSocket4) 1812 | theNativeSocket4 = nativeSocket; 1813 | else 1814 | theNativeSocket6 = nativeSocket; 1815 | 1816 | // Setup the CFSocket so that invalidating it will not close the underlying native socket 1817 | CFSocketSetSocketFlags(sock, 0); 1818 | 1819 | // Invalidate and release the CFSocket - All we need from here on out is the nativeSocket. 1820 | // Note: If we don't invalidate the CFSocket (leaving the native socket open) 1821 | // then theReadStream and theWriteStream won't function properly. 1822 | // Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted. 1823 | // 1824 | // This is likely due to the mixture of the CFSocketCreateWithNative method, 1825 | // along with the CFStreamCreatePairWithSocket method. 1826 | // The documentation for CFSocketCreateWithNative states: 1827 | // 1828 | // If a CFSocket object already exists for sock, 1829 | // the function returns the pre-existing object instead of creating a new object; 1830 | // the context, callout, and callBackTypes parameters are ignored in this case. 1831 | // 1832 | // So the CFStreamCreateWithNative method invokes the CFSocketCreateWithNative method, 1833 | // thinking that is creating a new underlying CFSocket for it's own purposes. 1834 | // When it does this, it uses the context/callout/callbackTypes parameters to setup everything appropriately. 1835 | // However, if a CFSocket already exists for the native socket, 1836 | // then it is returned (as per the documentation), which in turn screws up the CFStreams. 1837 | 1838 | CFSocketInvalidate(sock); 1839 | CFRelease(sock); 1840 | theSocket4 = NULL; 1841 | theSocket6 = NULL; 1842 | 1843 | NSError *err; 1844 | BOOL pass = YES; 1845 | 1846 | if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO; 1847 | if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO; 1848 | if(pass && ![self openStreamsAndReturnError:&err]) pass = NO; 1849 | 1850 | if(!pass) 1851 | { 1852 | [self closeWithError:err]; 1853 | } 1854 | } 1855 | 1856 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1857 | #pragma mark Stream Implementation 1858 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 1859 | 1860 | /** 1861 | * Creates the CFReadStream and CFWriteStream from the given native socket. 1862 | * The CFSocket may be extracted from either stream after the streams have been opened. 1863 | * 1864 | * Note: The given native socket must already be connected! 1865 | **/ 1866 | - (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr 1867 | { 1868 | // Create the socket & streams. 1869 | CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); 1870 | if (theReadStream == NULL || theWriteStream == NULL) 1871 | { 1872 | NSError *err = [self getStreamError]; 1873 | 1874 | NSLog(@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err); 1875 | 1876 | if (errPtr) *errPtr = err; 1877 | return NO; 1878 | } 1879 | 1880 | // Ensure the CF & BSD socket is closed when the streams are closed. 1881 | CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 1882 | CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 1883 | 1884 | return YES; 1885 | } 1886 | 1887 | /** 1888 | * Creates the CFReadStream and CFWriteStream from the given hostname and port number. 1889 | * The CFSocket may be extracted from either stream after the streams have been opened. 1890 | **/ 1891 | - (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr 1892 | { 1893 | // Create the socket & streams. 1894 | CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)hostname, port, &theReadStream, &theWriteStream); 1895 | if (theReadStream == NULL || theWriteStream == NULL) 1896 | { 1897 | if (errPtr) *errPtr = [self getStreamError]; 1898 | return NO; 1899 | } 1900 | 1901 | // Ensure the CF & BSD socket is closed when the streams are closed. 1902 | CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 1903 | CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); 1904 | 1905 | return YES; 1906 | } 1907 | 1908 | - (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr 1909 | { 1910 | // Get the CFRunLoop to which the socket should be attached. 1911 | theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; 1912 | 1913 | // Setup read stream callbacks 1914 | 1915 | CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | 1916 | kCFStreamEventErrorOccurred | 1917 | kCFStreamEventEndEncountered | 1918 | kCFStreamEventOpenCompleted; 1919 | 1920 | if (!CFReadStreamSetClient(theReadStream, 1921 | readStreamEvents, 1922 | (CFReadStreamClientCallBack)&MyCFReadStreamCallback, 1923 | (CFStreamClientContext *)(&theContext))) 1924 | { 1925 | NSError *err = [self getStreamError]; 1926 | 1927 | NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); 1928 | NSLog (@"Error: %@", err); 1929 | 1930 | if (errPtr) *errPtr = err; 1931 | return NO; 1932 | } 1933 | 1934 | // Setup write stream callbacks 1935 | 1936 | CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes | 1937 | kCFStreamEventErrorOccurred | 1938 | kCFStreamEventEndEncountered | 1939 | kCFStreamEventOpenCompleted; 1940 | 1941 | if (!CFWriteStreamSetClient (theWriteStream, 1942 | writeStreamEvents, 1943 | (CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, 1944 | (CFStreamClientContext *)(&theContext))) 1945 | { 1946 | NSError *err = [self getStreamError]; 1947 | 1948 | NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); 1949 | NSLog (@"Error: %@", err); 1950 | 1951 | if (errPtr) *errPtr = err; 1952 | return NO; 1953 | } 1954 | 1955 | // Add read and write streams to run loop 1956 | 1957 | for (NSString *runLoopMode in theRunLoopModes) 1958 | { 1959 | CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, (__bridge CFStringRef)runLoopMode); 1960 | CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, (__bridge CFStringRef)runLoopMode); 1961 | } 1962 | 1963 | return YES; 1964 | } 1965 | 1966 | /** 1967 | * Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. 1968 | * 1969 | * If being called from a connect method, 1970 | * the CFSocket and CFNativeSocket will not be available until after the connection is opened. 1971 | **/ 1972 | - (BOOL)configureStreamsAndReturnError:(NSError **)errPtr 1973 | { 1974 | // Call the delegate method for further configuration. 1975 | if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) 1976 | { 1977 | if([theDelegate onSocketWillConnect:self] == NO) 1978 | { 1979 | if (errPtr) *errPtr = [self getAbortError]; 1980 | return NO; 1981 | } 1982 | } 1983 | return YES; 1984 | } 1985 | 1986 | - (BOOL)openStreamsAndReturnError:(NSError **)errPtr 1987 | { 1988 | BOOL pass = YES; 1989 | 1990 | if(pass && !CFReadStreamOpen(theReadStream)) 1991 | { 1992 | NSLog (@"AsyncSocket %p couldn't open read stream,", self); 1993 | pass = NO; 1994 | } 1995 | 1996 | if(pass && !CFWriteStreamOpen(theWriteStream)) 1997 | { 1998 | NSLog (@"AsyncSocket %p couldn't open write stream,", self); 1999 | pass = NO; 2000 | } 2001 | 2002 | if(!pass) 2003 | { 2004 | if (errPtr) *errPtr = [self getStreamError]; 2005 | } 2006 | 2007 | return pass; 2008 | } 2009 | 2010 | /** 2011 | * Called when read or write streams open. 2012 | * When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. 2013 | **/ 2014 | - (void)doStreamOpen 2015 | { 2016 | if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite)) 2017 | { 2018 | NSError *err = nil; 2019 | 2020 | // Get the socket 2021 | if (![self setSocketFromStreamsAndReturnError: &err]) 2022 | { 2023 | NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); 2024 | [self closeWithError:err]; 2025 | return; 2026 | } 2027 | 2028 | // Stop the connection attempt timeout timer 2029 | [self endConnectTimeout]; 2030 | 2031 | if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) 2032 | { 2033 | [theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; 2034 | } 2035 | 2036 | // Immediately deal with any already-queued requests. 2037 | [self maybeDequeueRead]; 2038 | [self maybeDequeueWrite]; 2039 | } 2040 | } 2041 | 2042 | - (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr 2043 | { 2044 | // Get the CFSocketNativeHandle from theReadStream 2045 | CFSocketNativeHandle native; 2046 | CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); 2047 | if(nativeProp == NULL) 2048 | { 2049 | if (errPtr) *errPtr = [self getStreamError]; 2050 | return NO; 2051 | } 2052 | 2053 | CFIndex nativePropLen = CFDataGetLength(nativeProp); 2054 | CFIndex nativeLen = (CFIndex)sizeof(native); 2055 | 2056 | CFIndex len = MIN(nativePropLen, nativeLen); 2057 | 2058 | CFDataGetBytes(nativeProp, CFRangeMake(0, len), (UInt8 *)&native); 2059 | CFRelease(nativeProp); 2060 | 2061 | CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); 2062 | if(theSocket == NULL) 2063 | { 2064 | if (errPtr) *errPtr = [self getSocketError]; 2065 | return NO; 2066 | } 2067 | 2068 | // Determine whether the connection was IPv4 or IPv6. 2069 | // We may already know if this was an accepted socket, 2070 | // or if the connectToAddress method was used. 2071 | // In either of the above two cases, the native socket variable would already be set. 2072 | 2073 | if (theNativeSocket4 > 0) 2074 | { 2075 | theSocket4 = theSocket; 2076 | return YES; 2077 | } 2078 | if (theNativeSocket6 > 0) 2079 | { 2080 | theSocket6 = theSocket; 2081 | return YES; 2082 | } 2083 | 2084 | CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); 2085 | if(peeraddr == NULL) 2086 | { 2087 | NSLog(@"AsyncSocket couldn't determine IP version of socket"); 2088 | 2089 | CFRelease(theSocket); 2090 | 2091 | if (errPtr) *errPtr = [self getSocketError]; 2092 | return NO; 2093 | } 2094 | struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr); 2095 | 2096 | if(sa->sa_family == AF_INET) 2097 | { 2098 | theSocket4 = theSocket; 2099 | theNativeSocket4 = native; 2100 | } 2101 | else 2102 | { 2103 | theSocket6 = theSocket; 2104 | theNativeSocket6 = native; 2105 | } 2106 | 2107 | CFRelease(peeraddr); 2108 | 2109 | return YES; 2110 | } 2111 | 2112 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2113 | #pragma mark Disconnect Implementation 2114 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2115 | 2116 | // Sends error message and disconnects 2117 | - (void)closeWithError:(NSError *)err 2118 | { 2119 | theFlags |= kClosingWithError; 2120 | 2121 | if (theFlags & kDidStartDelegate) 2122 | { 2123 | // Try to salvage what data we can. 2124 | [self recoverUnreadData]; 2125 | 2126 | // Let the delegate know, so it can try to recover if it likes. 2127 | if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) 2128 | { 2129 | [theDelegate onSocket:self willDisconnectWithError:err]; 2130 | } 2131 | } 2132 | [self close]; 2133 | } 2134 | 2135 | // Prepare partially read data for recovery. 2136 | - (void)recoverUnreadData 2137 | { 2138 | if(theCurrentRead != nil) 2139 | { 2140 | // We never finished the current read. 2141 | // Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet. 2142 | 2143 | if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0)) 2144 | { 2145 | // We need to move its data into the front of the partial read buffer. 2146 | 2147 | void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; 2148 | 2149 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0) 2150 | withBytes:buffer 2151 | length:theCurrentRead->bytesDone]; 2152 | } 2153 | } 2154 | 2155 | [self emptyQueues]; 2156 | } 2157 | 2158 | - (void)emptyQueues 2159 | { 2160 | if (theCurrentRead != nil) [self endCurrentRead]; 2161 | if (theCurrentWrite != nil) [self endCurrentWrite]; 2162 | 2163 | [theReadQueue removeAllObjects]; 2164 | [theWriteQueue removeAllObjects]; 2165 | 2166 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; 2167 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; 2168 | 2169 | theFlags &= ~kDequeueReadScheduled; 2170 | theFlags &= ~kDequeueWriteScheduled; 2171 | } 2172 | 2173 | /** 2174 | * Disconnects. This is called for both error and clean disconnections. 2175 | **/ 2176 | - (void)close 2177 | { 2178 | // Empty queues 2179 | [self emptyQueues]; 2180 | 2181 | // Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error) 2182 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0]; 2183 | 2184 | [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; 2185 | 2186 | // Stop the connection attempt timeout timer 2187 | if (theConnectTimer != nil) 2188 | { 2189 | [self endConnectTimeout]; 2190 | } 2191 | 2192 | // Close streams. 2193 | if (theReadStream != NULL) 2194 | { 2195 | [self runLoopUnscheduleReadStream]; 2196 | CFReadStreamClose(theReadStream); 2197 | CFRelease(theReadStream); 2198 | theReadStream = NULL; 2199 | } 2200 | if (theWriteStream != NULL) 2201 | { 2202 | [self runLoopUnscheduleWriteStream]; 2203 | CFWriteStreamClose(theWriteStream); 2204 | CFRelease(theWriteStream); 2205 | theWriteStream = NULL; 2206 | } 2207 | 2208 | // Close sockets. 2209 | if (theSocket4 != NULL) 2210 | { 2211 | CFSocketInvalidate (theSocket4); 2212 | CFRelease (theSocket4); 2213 | theSocket4 = NULL; 2214 | } 2215 | if (theSocket6 != NULL) 2216 | { 2217 | CFSocketInvalidate (theSocket6); 2218 | CFRelease (theSocket6); 2219 | theSocket6 = NULL; 2220 | } 2221 | 2222 | // Closing the streams or sockets resulted in closing the underlying native socket 2223 | theNativeSocket4 = 0; 2224 | theNativeSocket6 = 0; 2225 | 2226 | // Remove run loop sources 2227 | if (theSource4 != NULL) 2228 | { 2229 | [self runLoopRemoveSource:theSource4]; 2230 | CFRelease (theSource4); 2231 | theSource4 = NULL; 2232 | } 2233 | if (theSource6 != NULL) 2234 | { 2235 | [self runLoopRemoveSource:theSource6]; 2236 | CFRelease (theSource6); 2237 | theSource6 = NULL; 2238 | } 2239 | theRunLoop = NULL; 2240 | 2241 | // If the client has passed the connect/accept method, then the connection has at least begun. 2242 | // Notify delegate that it is now ending. 2243 | BOOL shouldCallDelegate = (theFlags & kDidStartDelegate); 2244 | 2245 | // Clear all flags (except the pre-buffering flag, which should remain as is) 2246 | theFlags &= kEnablePreBuffering; 2247 | 2248 | if (shouldCallDelegate) 2249 | { 2250 | if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) 2251 | { 2252 | [theDelegate onSocketDidDisconnect:self]; 2253 | } 2254 | } 2255 | 2256 | // Do not access any instance variables after calling onSocketDidDisconnect. 2257 | // This gives the delegate freedom to release us without returning here and crashing. 2258 | } 2259 | 2260 | /** 2261 | * Disconnects immediately. Any pending reads or writes are dropped. 2262 | **/ 2263 | - (void)disconnect 2264 | { 2265 | #if DEBUG_THREAD_SAFETY 2266 | [self checkForThreadSafety]; 2267 | #endif 2268 | 2269 | [self close]; 2270 | } 2271 | 2272 | /** 2273 | * Diconnects after all pending reads have completed. 2274 | **/ 2275 | - (void)disconnectAfterReading 2276 | { 2277 | #if DEBUG_THREAD_SAFETY 2278 | [self checkForThreadSafety]; 2279 | #endif 2280 | 2281 | theFlags |= (kForbidReadsWrites | kDisconnectAfterReads); 2282 | 2283 | [self maybeScheduleDisconnect]; 2284 | } 2285 | 2286 | /** 2287 | * Disconnects after all pending writes have completed. 2288 | **/ 2289 | - (void)disconnectAfterWriting 2290 | { 2291 | #if DEBUG_THREAD_SAFETY 2292 | [self checkForThreadSafety]; 2293 | #endif 2294 | 2295 | theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites); 2296 | 2297 | [self maybeScheduleDisconnect]; 2298 | } 2299 | 2300 | /** 2301 | * Disconnects after all pending reads and writes have completed. 2302 | **/ 2303 | - (void)disconnectAfterReadingAndWriting 2304 | { 2305 | #if DEBUG_THREAD_SAFETY 2306 | [self checkForThreadSafety]; 2307 | #endif 2308 | 2309 | theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); 2310 | 2311 | [self maybeScheduleDisconnect]; 2312 | } 2313 | 2314 | /** 2315 | * Schedules a call to disconnect if possible. 2316 | * That is, if all writes have completed, and we're set to disconnect after writing, 2317 | * or if all reads have completed, and we're set to disconnect after reading. 2318 | **/ 2319 | - (void)maybeScheduleDisconnect 2320 | { 2321 | BOOL shouldDisconnect = NO; 2322 | 2323 | if(theFlags & kDisconnectAfterReads) 2324 | { 2325 | if(([theReadQueue count] == 0) && (theCurrentRead == nil)) 2326 | { 2327 | if(theFlags & kDisconnectAfterWrites) 2328 | { 2329 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) 2330 | { 2331 | shouldDisconnect = YES; 2332 | } 2333 | } 2334 | else 2335 | { 2336 | shouldDisconnect = YES; 2337 | } 2338 | } 2339 | } 2340 | else if(theFlags & kDisconnectAfterWrites) 2341 | { 2342 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) 2343 | { 2344 | shouldDisconnect = YES; 2345 | } 2346 | } 2347 | 2348 | if(shouldDisconnect) 2349 | { 2350 | [self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 2351 | } 2352 | } 2353 | 2354 | /** 2355 | * In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read 2356 | * any data that's left on the socket. 2357 | **/ 2358 | - (NSData *)unreadData 2359 | { 2360 | #if DEBUG_THREAD_SAFETY 2361 | [self checkForThreadSafety]; 2362 | #endif 2363 | 2364 | // Ensure this method will only return data in the event of an error 2365 | if (!(theFlags & kClosingWithError)) return nil; 2366 | 2367 | if (theReadStream == NULL) return nil; 2368 | 2369 | NSUInteger totalBytesRead = [partialReadBuffer length]; 2370 | 2371 | BOOL error = NO; 2372 | while (!error && CFReadStreamHasBytesAvailable(theReadStream)) 2373 | { 2374 | if (totalBytesRead == [partialReadBuffer length]) 2375 | { 2376 | [partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE]; 2377 | } 2378 | 2379 | // Number of bytes to read is space left in packet buffer. 2380 | NSUInteger bytesToRead = [partialReadBuffer length] - totalBytesRead; 2381 | 2382 | // Read data into packet buffer 2383 | UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead ); 2384 | 2385 | CFIndex result = CFReadStreamRead(theReadStream, packetbuf, bytesToRead); 2386 | 2387 | // Check results 2388 | if (result < 0) 2389 | { 2390 | error = YES; 2391 | } 2392 | else 2393 | { 2394 | CFIndex bytesRead = result; 2395 | 2396 | totalBytesRead += bytesRead; 2397 | } 2398 | } 2399 | 2400 | [partialReadBuffer setLength:totalBytesRead]; 2401 | 2402 | return partialReadBuffer; 2403 | } 2404 | 2405 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2406 | #pragma mark Errors 2407 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2408 | 2409 | /** 2410 | * Returns a standard error object for the current errno value. 2411 | * Errno is used for low-level BSD socket errors. 2412 | **/ 2413 | - (NSError *)getErrnoError 2414 | { 2415 | NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; 2416 | NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; 2417 | 2418 | return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; 2419 | } 2420 | 2421 | /** 2422 | * Returns a standard error message for a CFSocket error. 2423 | * Unfortunately, CFSocket offers no feedback on its errors. 2424 | **/ 2425 | - (NSError *)getSocketError 2426 | { 2427 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", 2428 | @"AsyncSocket", [NSBundle mainBundle], 2429 | @"General CFSocket error", nil); 2430 | 2431 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2432 | 2433 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; 2434 | } 2435 | 2436 | - (NSError *)getStreamError 2437 | { 2438 | CFStreamError err; 2439 | if (theReadStream != NULL) 2440 | { 2441 | err = CFReadStreamGetError (theReadStream); 2442 | if (err.error != 0) return [self errorFromCFStreamError: err]; 2443 | } 2444 | 2445 | if (theWriteStream != NULL) 2446 | { 2447 | err = CFWriteStreamGetError (theWriteStream); 2448 | if (err.error != 0) return [self errorFromCFStreamError: err]; 2449 | } 2450 | 2451 | return nil; 2452 | } 2453 | 2454 | /** 2455 | * Returns a standard AsyncSocket abort error. 2456 | **/ 2457 | - (NSError *)getAbortError 2458 | { 2459 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", 2460 | @"AsyncSocket", [NSBundle mainBundle], 2461 | @"Connection canceled", nil); 2462 | 2463 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2464 | 2465 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; 2466 | } 2467 | 2468 | /** 2469 | * Returns a standard AsyncSocket connect timeout error. 2470 | **/ 2471 | - (NSError *)getConnectTimeoutError 2472 | { 2473 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError", 2474 | @"AsyncSocket", [NSBundle mainBundle], 2475 | @"Attempt to connect to host timed out", nil); 2476 | 2477 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2478 | 2479 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info]; 2480 | } 2481 | 2482 | /** 2483 | * Returns a standard AsyncSocket maxed out error. 2484 | **/ 2485 | - (NSError *)getReadMaxedOutError 2486 | { 2487 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError", 2488 | @"AsyncSocket", [NSBundle mainBundle], 2489 | @"Read operation reached set maximum length", nil); 2490 | 2491 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2492 | 2493 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info]; 2494 | } 2495 | 2496 | /** 2497 | * Returns a standard AsyncSocket read timeout error. 2498 | **/ 2499 | - (NSError *)getReadTimeoutError 2500 | { 2501 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", 2502 | @"AsyncSocket", [NSBundle mainBundle], 2503 | @"Read operation timed out", nil); 2504 | 2505 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2506 | 2507 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; 2508 | } 2509 | 2510 | /** 2511 | * Returns a standard AsyncSocket write timeout error. 2512 | **/ 2513 | - (NSError *)getWriteTimeoutError 2514 | { 2515 | NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", 2516 | @"AsyncSocket", [NSBundle mainBundle], 2517 | @"Write operation timed out", nil); 2518 | 2519 | NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; 2520 | 2521 | return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; 2522 | } 2523 | 2524 | - (NSError *)errorFromCFStreamError:(CFStreamError)err 2525 | { 2526 | if (err.domain == 0 && err.error == 0) return nil; 2527 | 2528 | // Can't use switch; these constants aren't int literals. 2529 | NSString *domain = @"CFStreamError (unlisted domain)"; 2530 | NSString *message = nil; 2531 | 2532 | if(err.domain == kCFStreamErrorDomainPOSIX) { 2533 | domain = NSPOSIXErrorDomain; 2534 | } 2535 | else if(err.domain == kCFStreamErrorDomainMacOSStatus) { 2536 | domain = NSOSStatusErrorDomain; 2537 | } 2538 | else if(err.domain == kCFStreamErrorDomainMach) { 2539 | domain = NSMachErrorDomain; 2540 | } 2541 | else if(err.domain == kCFStreamErrorDomainNetDB) 2542 | { 2543 | domain = @"kCFStreamErrorDomainNetDB"; 2544 | message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; 2545 | } 2546 | else if(err.domain == kCFStreamErrorDomainNetServices) { 2547 | domain = @"kCFStreamErrorDomainNetServices"; 2548 | } 2549 | else if(err.domain == kCFStreamErrorDomainSOCKS) { 2550 | domain = @"kCFStreamErrorDomainSOCKS"; 2551 | } 2552 | else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { 2553 | domain = @"kCFStreamErrorDomainSystemConfiguration"; 2554 | } 2555 | else if(err.domain == kCFStreamErrorDomainSSL) { 2556 | domain = @"kCFStreamErrorDomainSSL"; 2557 | } 2558 | 2559 | NSDictionary *info = nil; 2560 | if(message != nil) 2561 | { 2562 | info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; 2563 | } 2564 | return [NSError errorWithDomain:domain code:err.error userInfo:info]; 2565 | } 2566 | 2567 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2568 | #pragma mark Diagnostics 2569 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 2570 | 2571 | - (BOOL)isDisconnected 2572 | { 2573 | #if DEBUG_THREAD_SAFETY 2574 | [self checkForThreadSafety]; 2575 | #endif 2576 | 2577 | if (theNativeSocket4 > 0) return NO; 2578 | if (theNativeSocket6 > 0) return NO; 2579 | 2580 | if (theSocket4) return NO; 2581 | if (theSocket6) return NO; 2582 | 2583 | if (theReadStream) return NO; 2584 | if (theWriteStream) return NO; 2585 | 2586 | return YES; 2587 | } 2588 | 2589 | - (BOOL)isConnected 2590 | { 2591 | #if DEBUG_THREAD_SAFETY 2592 | [self checkForThreadSafety]; 2593 | #endif 2594 | 2595 | return [self areStreamsConnected]; 2596 | } 2597 | 2598 | - (NSString *)connectedHost 2599 | { 2600 | #if DEBUG_THREAD_SAFETY 2601 | [self checkForThreadSafety]; 2602 | #endif 2603 | 2604 | if(theSocket4) 2605 | return [self connectedHostFromCFSocket4:theSocket4]; 2606 | if(theSocket6) 2607 | return [self connectedHostFromCFSocket6:theSocket6]; 2608 | 2609 | if(theNativeSocket4 > 0) 2610 | return [self connectedHostFromNativeSocket4:theNativeSocket4]; 2611 | if(theNativeSocket6 > 0) 2612 | return [self connectedHostFromNativeSocket6:theNativeSocket6]; 2613 | 2614 | return nil; 2615 | } 2616 | 2617 | - (UInt16)connectedPort 2618 | { 2619 | #if DEBUG_THREAD_SAFETY 2620 | [self checkForThreadSafety]; 2621 | #endif 2622 | 2623 | if(theSocket4) 2624 | return [self connectedPortFromCFSocket4:theSocket4]; 2625 | if(theSocket6) 2626 | return [self connectedPortFromCFSocket6:theSocket6]; 2627 | 2628 | if(theNativeSocket4 > 0) 2629 | return [self connectedPortFromNativeSocket4:theNativeSocket4]; 2630 | if(theNativeSocket6 > 0) 2631 | return [self connectedPortFromNativeSocket6:theNativeSocket6]; 2632 | 2633 | return 0; 2634 | } 2635 | 2636 | - (NSString *)localHost 2637 | { 2638 | #if DEBUG_THREAD_SAFETY 2639 | [self checkForThreadSafety]; 2640 | #endif 2641 | 2642 | if(theSocket4) 2643 | return [self localHostFromCFSocket4:theSocket4]; 2644 | if(theSocket6) 2645 | return [self localHostFromCFSocket6:theSocket6]; 2646 | 2647 | if(theNativeSocket4 > 0) 2648 | return [self localHostFromNativeSocket4:theNativeSocket4]; 2649 | if(theNativeSocket6 > 0) 2650 | return [self localHostFromNativeSocket6:theNativeSocket6]; 2651 | 2652 | return nil; 2653 | } 2654 | 2655 | - (UInt16)localPort 2656 | { 2657 | #if DEBUG_THREAD_SAFETY 2658 | [self checkForThreadSafety]; 2659 | #endif 2660 | 2661 | if(theSocket4) 2662 | return [self localPortFromCFSocket4:theSocket4]; 2663 | if(theSocket6) 2664 | return [self localPortFromCFSocket6:theSocket6]; 2665 | 2666 | if(theNativeSocket4 > 0) 2667 | return [self localPortFromNativeSocket4:theNativeSocket4]; 2668 | if(theNativeSocket6 > 0) 2669 | return [self localPortFromNativeSocket6:theNativeSocket6]; 2670 | 2671 | return 0; 2672 | } 2673 | 2674 | - (NSString *)connectedHost4 2675 | { 2676 | if(theSocket4) 2677 | return [self connectedHostFromCFSocket4:theSocket4]; 2678 | if(theNativeSocket4 > 0) 2679 | return [self connectedHostFromNativeSocket4:theNativeSocket4]; 2680 | 2681 | return nil; 2682 | } 2683 | 2684 | - (NSString *)connectedHost6 2685 | { 2686 | if(theSocket6) 2687 | return [self connectedHostFromCFSocket6:theSocket6]; 2688 | if(theNativeSocket6 > 0) 2689 | return [self connectedHostFromNativeSocket6:theNativeSocket6]; 2690 | 2691 | return nil; 2692 | } 2693 | 2694 | - (UInt16)connectedPort4 2695 | { 2696 | if(theSocket4) 2697 | return [self connectedPortFromCFSocket4:theSocket4]; 2698 | if(theNativeSocket4 > 0) 2699 | return [self connectedPortFromNativeSocket4:theNativeSocket4]; 2700 | 2701 | return 0; 2702 | } 2703 | 2704 | - (UInt16)connectedPort6 2705 | { 2706 | if(theSocket6) 2707 | return [self connectedPortFromCFSocket6:theSocket6]; 2708 | if(theNativeSocket6 > 0) 2709 | return [self connectedPortFromNativeSocket6:theNativeSocket6]; 2710 | 2711 | return 0; 2712 | } 2713 | 2714 | - (NSString *)localHost4 2715 | { 2716 | if(theSocket4) 2717 | return [self localHostFromCFSocket4:theSocket4]; 2718 | if(theNativeSocket4 > 0) 2719 | return [self localHostFromNativeSocket4:theNativeSocket4]; 2720 | 2721 | return nil; 2722 | } 2723 | 2724 | - (NSString *)localHost6 2725 | { 2726 | if(theSocket6) 2727 | return [self localHostFromCFSocket6:theSocket6]; 2728 | if(theNativeSocket6 > 0) 2729 | return [self localHostFromNativeSocket6:theNativeSocket6]; 2730 | 2731 | return nil; 2732 | } 2733 | 2734 | - (UInt16)localPort4 2735 | { 2736 | if(theSocket4) 2737 | return [self localPortFromCFSocket4:theSocket4]; 2738 | if(theNativeSocket4 > 0) 2739 | return [self localPortFromNativeSocket4:theNativeSocket4]; 2740 | 2741 | return 0; 2742 | } 2743 | 2744 | - (UInt16)localPort6 2745 | { 2746 | if(theSocket6) 2747 | return [self localPortFromCFSocket6:theSocket6]; 2748 | if(theNativeSocket6 > 0) 2749 | return [self localPortFromNativeSocket6:theNativeSocket6]; 2750 | 2751 | return 0; 2752 | } 2753 | 2754 | - (NSString *)connectedHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket 2755 | { 2756 | struct sockaddr_in sockaddr4; 2757 | socklen_t sockaddr4len = sizeof(sockaddr4); 2758 | 2759 | if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 2760 | { 2761 | return nil; 2762 | } 2763 | return [self hostFromAddress4:&sockaddr4]; 2764 | } 2765 | 2766 | - (NSString *)connectedHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket 2767 | { 2768 | struct sockaddr_in6 sockaddr6; 2769 | socklen_t sockaddr6len = sizeof(sockaddr6); 2770 | 2771 | if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 2772 | { 2773 | return nil; 2774 | } 2775 | return [self hostFromAddress6:&sockaddr6]; 2776 | } 2777 | 2778 | - (NSString *)connectedHostFromCFSocket4:(CFSocketRef)theSocket 2779 | { 2780 | CFDataRef peeraddr; 2781 | NSString *peerstr = nil; 2782 | 2783 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) 2784 | { 2785 | struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); 2786 | 2787 | peerstr = [self hostFromAddress4:pSockAddr]; 2788 | CFRelease (peeraddr); 2789 | } 2790 | 2791 | return peerstr; 2792 | } 2793 | 2794 | - (NSString *)connectedHostFromCFSocket6:(CFSocketRef)theSocket 2795 | { 2796 | CFDataRef peeraddr; 2797 | NSString *peerstr = nil; 2798 | 2799 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) 2800 | { 2801 | struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); 2802 | 2803 | peerstr = [self hostFromAddress6:pSockAddr]; 2804 | CFRelease (peeraddr); 2805 | } 2806 | 2807 | return peerstr; 2808 | } 2809 | 2810 | - (UInt16)connectedPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket 2811 | { 2812 | struct sockaddr_in sockaddr4; 2813 | socklen_t sockaddr4len = sizeof(sockaddr4); 2814 | 2815 | if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 2816 | { 2817 | return 0; 2818 | } 2819 | return [self portFromAddress4:&sockaddr4]; 2820 | } 2821 | 2822 | - (UInt16)connectedPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket 2823 | { 2824 | struct sockaddr_in6 sockaddr6; 2825 | socklen_t sockaddr6len = sizeof(sockaddr6); 2826 | 2827 | if(getpeername(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 2828 | { 2829 | return 0; 2830 | } 2831 | return [self portFromAddress6:&sockaddr6]; 2832 | } 2833 | 2834 | - (UInt16)connectedPortFromCFSocket4:(CFSocketRef)theSocket 2835 | { 2836 | CFDataRef peeraddr; 2837 | UInt16 peerport = 0; 2838 | 2839 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) 2840 | { 2841 | struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(peeraddr); 2842 | 2843 | peerport = [self portFromAddress4:pSockAddr]; 2844 | CFRelease (peeraddr); 2845 | } 2846 | 2847 | return peerport; 2848 | } 2849 | 2850 | - (UInt16)connectedPortFromCFSocket6:(CFSocketRef)theSocket 2851 | { 2852 | CFDataRef peeraddr; 2853 | UInt16 peerport = 0; 2854 | 2855 | if((peeraddr = CFSocketCopyPeerAddress(theSocket))) 2856 | { 2857 | struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(peeraddr); 2858 | 2859 | peerport = [self portFromAddress6:pSockAddr]; 2860 | CFRelease (peeraddr); 2861 | } 2862 | 2863 | return peerport; 2864 | } 2865 | 2866 | - (NSString *)localHostFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket 2867 | { 2868 | struct sockaddr_in sockaddr4; 2869 | socklen_t sockaddr4len = sizeof(sockaddr4); 2870 | 2871 | if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 2872 | { 2873 | return nil; 2874 | } 2875 | return [self hostFromAddress4:&sockaddr4]; 2876 | } 2877 | 2878 | - (NSString *)localHostFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket 2879 | { 2880 | struct sockaddr_in6 sockaddr6; 2881 | socklen_t sockaddr6len = sizeof(sockaddr6); 2882 | 2883 | if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 2884 | { 2885 | return nil; 2886 | } 2887 | return [self hostFromAddress6:&sockaddr6]; 2888 | } 2889 | 2890 | - (NSString *)localHostFromCFSocket4:(CFSocketRef)theSocket 2891 | { 2892 | CFDataRef selfaddr; 2893 | NSString *selfstr = nil; 2894 | 2895 | if((selfaddr = CFSocketCopyAddress(theSocket))) 2896 | { 2897 | struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); 2898 | 2899 | selfstr = [self hostFromAddress4:pSockAddr]; 2900 | CFRelease (selfaddr); 2901 | } 2902 | 2903 | return selfstr; 2904 | } 2905 | 2906 | - (NSString *)localHostFromCFSocket6:(CFSocketRef)theSocket 2907 | { 2908 | CFDataRef selfaddr; 2909 | NSString *selfstr = nil; 2910 | 2911 | if((selfaddr = CFSocketCopyAddress(theSocket))) 2912 | { 2913 | struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); 2914 | 2915 | selfstr = [self hostFromAddress6:pSockAddr]; 2916 | CFRelease (selfaddr); 2917 | } 2918 | 2919 | return selfstr; 2920 | } 2921 | 2922 | - (UInt16)localPortFromNativeSocket4:(CFSocketNativeHandle)theNativeSocket 2923 | { 2924 | struct sockaddr_in sockaddr4; 2925 | socklen_t sockaddr4len = sizeof(sockaddr4); 2926 | 2927 | if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr4, &sockaddr4len) < 0) 2928 | { 2929 | return 0; 2930 | } 2931 | return [self portFromAddress4:&sockaddr4]; 2932 | } 2933 | 2934 | - (UInt16)localPortFromNativeSocket6:(CFSocketNativeHandle)theNativeSocket 2935 | { 2936 | struct sockaddr_in6 sockaddr6; 2937 | socklen_t sockaddr6len = sizeof(sockaddr6); 2938 | 2939 | if(getsockname(theNativeSocket, (struct sockaddr *)&sockaddr6, &sockaddr6len) < 0) 2940 | { 2941 | return 0; 2942 | } 2943 | return [self portFromAddress6:&sockaddr6]; 2944 | } 2945 | 2946 | - (UInt16)localPortFromCFSocket4:(CFSocketRef)theSocket 2947 | { 2948 | CFDataRef selfaddr; 2949 | UInt16 selfport = 0; 2950 | 2951 | if ((selfaddr = CFSocketCopyAddress(theSocket))) 2952 | { 2953 | struct sockaddr_in *pSockAddr = (struct sockaddr_in *)CFDataGetBytePtr(selfaddr); 2954 | 2955 | selfport = [self portFromAddress4:pSockAddr]; 2956 | CFRelease (selfaddr); 2957 | } 2958 | 2959 | return selfport; 2960 | } 2961 | 2962 | - (UInt16)localPortFromCFSocket6:(CFSocketRef)theSocket 2963 | { 2964 | CFDataRef selfaddr; 2965 | UInt16 selfport = 0; 2966 | 2967 | if ((selfaddr = CFSocketCopyAddress(theSocket))) 2968 | { 2969 | struct sockaddr_in6 *pSockAddr = (struct sockaddr_in6 *)CFDataGetBytePtr(selfaddr); 2970 | 2971 | selfport = [self portFromAddress6:pSockAddr]; 2972 | CFRelease (selfaddr); 2973 | } 2974 | 2975 | return selfport; 2976 | } 2977 | 2978 | - (NSString *)hostFromAddress4:(struct sockaddr_in *)pSockaddr4 2979 | { 2980 | char addrBuf[INET_ADDRSTRLEN]; 2981 | 2982 | if(inet_ntop(AF_INET, &pSockaddr4->sin_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) 2983 | { 2984 | [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv4 address to string."]; 2985 | } 2986 | 2987 | return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; 2988 | } 2989 | 2990 | - (NSString *)hostFromAddress6:(struct sockaddr_in6 *)pSockaddr6 2991 | { 2992 | char addrBuf[INET6_ADDRSTRLEN]; 2993 | 2994 | if(inet_ntop(AF_INET6, &pSockaddr6->sin6_addr, addrBuf, (socklen_t)sizeof(addrBuf)) == NULL) 2995 | { 2996 | [NSException raise:NSInternalInconsistencyException format:@"Cannot convert IPv6 address to string."]; 2997 | } 2998 | 2999 | return [NSString stringWithCString:addrBuf encoding:NSASCIIStringEncoding]; 3000 | } 3001 | 3002 | - (UInt16)portFromAddress4:(struct sockaddr_in *)pSockaddr4 3003 | { 3004 | return ntohs(pSockaddr4->sin_port); 3005 | } 3006 | 3007 | - (UInt16)portFromAddress6:(struct sockaddr_in6 *)pSockaddr6 3008 | { 3009 | return ntohs(pSockaddr6->sin6_port); 3010 | } 3011 | 3012 | - (NSData *)connectedAddress 3013 | { 3014 | #if DEBUG_THREAD_SAFETY 3015 | [self checkForThreadSafety]; 3016 | #endif 3017 | 3018 | // Extract address from CFSocket 3019 | 3020 | CFSocketRef theSocket; 3021 | 3022 | if (theSocket4) 3023 | theSocket = theSocket4; 3024 | else 3025 | theSocket = theSocket6; 3026 | 3027 | if (theSocket) 3028 | { 3029 | CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); 3030 | 3031 | if (peeraddr == NULL) return nil; 3032 | 3033 | NSData *result = (__bridge_transfer NSData *)peeraddr; 3034 | return result; 3035 | } 3036 | 3037 | // Extract address from CFSocketNativeHandle 3038 | 3039 | socklen_t sockaddrlen; 3040 | CFSocketNativeHandle theNativeSocket = 0; 3041 | 3042 | if (theNativeSocket4 > 0) 3043 | { 3044 | theNativeSocket = theNativeSocket4; 3045 | sockaddrlen = sizeof(struct sockaddr_in); 3046 | } 3047 | else 3048 | { 3049 | theNativeSocket = theNativeSocket6; 3050 | sockaddrlen = sizeof(struct sockaddr_in6); 3051 | } 3052 | 3053 | NSData *result = nil; 3054 | void *sockaddr = malloc(sockaddrlen); 3055 | 3056 | if(getpeername(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) 3057 | { 3058 | result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; 3059 | } 3060 | else 3061 | { 3062 | free(sockaddr); 3063 | } 3064 | 3065 | return result; 3066 | } 3067 | 3068 | - (NSData *)localAddress 3069 | { 3070 | #if DEBUG_THREAD_SAFETY 3071 | [self checkForThreadSafety]; 3072 | #endif 3073 | 3074 | // Extract address from CFSocket 3075 | 3076 | CFSocketRef theSocket; 3077 | 3078 | if (theSocket4) 3079 | theSocket = theSocket4; 3080 | else 3081 | theSocket = theSocket6; 3082 | 3083 | if (theSocket) 3084 | { 3085 | CFDataRef selfaddr = CFSocketCopyAddress(theSocket); 3086 | 3087 | if (selfaddr == NULL) return nil; 3088 | 3089 | NSData *result = (__bridge_transfer NSData *)selfaddr; 3090 | return result; 3091 | } 3092 | 3093 | // Extract address from CFSocketNativeHandle 3094 | 3095 | socklen_t sockaddrlen; 3096 | CFSocketNativeHandle theNativeSocket = 0; 3097 | 3098 | if (theNativeSocket4 > 0) 3099 | { 3100 | theNativeSocket = theNativeSocket4; 3101 | sockaddrlen = sizeof(struct sockaddr_in); 3102 | } 3103 | else 3104 | { 3105 | theNativeSocket = theNativeSocket6; 3106 | sockaddrlen = sizeof(struct sockaddr_in6); 3107 | } 3108 | 3109 | NSData *result = nil; 3110 | void *sockaddr = malloc(sockaddrlen); 3111 | 3112 | if(getsockname(theNativeSocket, (struct sockaddr *)sockaddr, &sockaddrlen) >= 0) 3113 | { 3114 | result = [NSData dataWithBytesNoCopy:sockaddr length:sockaddrlen freeWhenDone:YES]; 3115 | } 3116 | else 3117 | { 3118 | free(sockaddr); 3119 | } 3120 | 3121 | return result; 3122 | } 3123 | 3124 | - (BOOL)isIPv4 3125 | { 3126 | #if DEBUG_THREAD_SAFETY 3127 | [self checkForThreadSafety]; 3128 | #endif 3129 | 3130 | return (theNativeSocket4 > 0 || theSocket4 != NULL); 3131 | } 3132 | 3133 | - (BOOL)isIPv6 3134 | { 3135 | #if DEBUG_THREAD_SAFETY 3136 | [self checkForThreadSafety]; 3137 | #endif 3138 | 3139 | return (theNativeSocket6 > 0 || theSocket6 != NULL); 3140 | } 3141 | 3142 | - (BOOL)areStreamsConnected 3143 | { 3144 | CFStreamStatus s; 3145 | 3146 | if (theReadStream != NULL) 3147 | { 3148 | s = CFReadStreamGetStatus(theReadStream); 3149 | if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) 3150 | return NO; 3151 | } 3152 | else return NO; 3153 | 3154 | if (theWriteStream != NULL) 3155 | { 3156 | s = CFWriteStreamGetStatus(theWriteStream); 3157 | if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) 3158 | return NO; 3159 | } 3160 | else return NO; 3161 | 3162 | return YES; 3163 | } 3164 | 3165 | - (NSString *)description 3166 | { 3167 | #if DEBUG_THREAD_SAFETY 3168 | [self checkForThreadSafety]; 3169 | #endif 3170 | 3171 | static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"}; 3172 | CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0; 3173 | CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0; 3174 | 3175 | NSString *peerstr, *selfstr; 3176 | 3177 | BOOL is4 = [self isIPv4]; 3178 | BOOL is6 = [self isIPv6]; 3179 | 3180 | if (is4 || is6) 3181 | { 3182 | if (is4 && is6) 3183 | { 3184 | peerstr = [NSString stringWithFormat: @"%@/%@ %u", 3185 | [self connectedHost4], 3186 | [self connectedHost6], 3187 | [self connectedPort]]; 3188 | } 3189 | else if (is4) 3190 | { 3191 | peerstr = [NSString stringWithFormat: @"%@ %u", 3192 | [self connectedHost4], 3193 | [self connectedPort4]]; 3194 | } 3195 | else 3196 | { 3197 | peerstr = [NSString stringWithFormat: @"%@ %u", 3198 | [self connectedHost6], 3199 | [self connectedPort6]]; 3200 | } 3201 | } 3202 | else peerstr = @"nowhere"; 3203 | 3204 | if (is4 || is6) 3205 | { 3206 | if (is4 && is6) 3207 | { 3208 | selfstr = [NSString stringWithFormat: @"%@/%@ %u", 3209 | [self localHost4], 3210 | [self localHost6], 3211 | [self localPort]]; 3212 | } 3213 | else if (is4) 3214 | { 3215 | selfstr = [NSString stringWithFormat: @"%@ %u", 3216 | [self localHost4], 3217 | [self localPort4]]; 3218 | } 3219 | else 3220 | { 3221 | selfstr = [NSString stringWithFormat: @"%@ %u", 3222 | [self localHost6], 3223 | [self localPort6]]; 3224 | } 3225 | } 3226 | else selfstr = @"nowhere"; 3227 | 3228 | NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150]; 3229 | 3230 | [ms appendString:[NSString stringWithFormat:@"readLength > 0) 3244 | percentDone = (float)theCurrentRead->bytesDone / (float)theCurrentRead->readLength * 100.0F; 3245 | else 3246 | percentDone = 100.0F; 3247 | 3248 | [ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", 3249 | (unsigned int)[theCurrentRead->buffer length], 3250 | theCurrentRead->bytesDone ? percentDone : 0]]; 3251 | } 3252 | 3253 | if (theCurrentWrite == nil || [theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) 3254 | [ms appendString: @"no current write, "]; 3255 | else 3256 | { 3257 | int percentDone = (float)theCurrentWrite->bytesDone / (float)[theCurrentWrite->buffer length] * 100.0F; 3258 | 3259 | [ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", 3260 | (unsigned int)[theCurrentWrite->buffer length], 3261 | theCurrentWrite->bytesDone ? percentDone : 0]]; 3262 | } 3263 | 3264 | [ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]]; 3265 | [ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]]; 3266 | 3267 | if(theFlags & kDisconnectAfterReads) 3268 | { 3269 | if(theFlags & kDisconnectAfterWrites) 3270 | [ms appendString: @", will disconnect after reads & writes"]; 3271 | else 3272 | [ms appendString: @", will disconnect after reads"]; 3273 | } 3274 | else if(theFlags & kDisconnectAfterWrites) 3275 | { 3276 | [ms appendString: @", will disconnect after writes"]; 3277 | } 3278 | 3279 | if (![self isConnected]) [ms appendString: @", not connected"]; 3280 | 3281 | [ms appendString:@">"]; 3282 | 3283 | return ms; 3284 | } 3285 | 3286 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3287 | #pragma mark Reading 3288 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3289 | 3290 | - (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag 3291 | { 3292 | [self readDataWithTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; 3293 | } 3294 | 3295 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 3296 | buffer:(NSMutableData *)buffer 3297 | bufferOffset:(NSUInteger)offset 3298 | tag:(long)tag 3299 | { 3300 | [self readDataWithTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; 3301 | } 3302 | 3303 | - (void)readDataWithTimeout:(NSTimeInterval)timeout 3304 | buffer:(NSMutableData *)buffer 3305 | bufferOffset:(NSUInteger)offset 3306 | maxLength:(NSUInteger)length 3307 | tag:(long)tag 3308 | { 3309 | #if DEBUG_THREAD_SAFETY 3310 | [self checkForThreadSafety]; 3311 | #endif 3312 | 3313 | if (offset > [buffer length]) return; 3314 | if (theFlags & kForbidReadsWrites) return; 3315 | 3316 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer 3317 | startOffset:offset 3318 | maxLength:length 3319 | timeout:timeout 3320 | readLength:0 3321 | terminator:nil 3322 | tag:tag]; 3323 | [theReadQueue addObject:packet]; 3324 | [self scheduleDequeueRead]; 3325 | 3326 | } 3327 | 3328 | - (void)readDataToLength:(NSUInteger)length withTimeout:(NSTimeInterval)timeout tag:(long)tag 3329 | { 3330 | [self readDataToLength:length withTimeout:timeout buffer:nil bufferOffset:0 tag:tag]; 3331 | } 3332 | 3333 | - (void)readDataToLength:(NSUInteger)length 3334 | withTimeout:(NSTimeInterval)timeout 3335 | buffer:(NSMutableData *)buffer 3336 | bufferOffset:(NSUInteger)offset 3337 | tag:(long)tag 3338 | { 3339 | #if DEBUG_THREAD_SAFETY 3340 | [self checkForThreadSafety]; 3341 | #endif 3342 | 3343 | if (length == 0) return; 3344 | if (offset > [buffer length]) return; 3345 | if (theFlags & kForbidReadsWrites) return; 3346 | 3347 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer 3348 | startOffset:offset 3349 | maxLength:0 3350 | timeout:timeout 3351 | readLength:length 3352 | terminator:nil 3353 | tag:tag]; 3354 | [theReadQueue addObject:packet]; 3355 | [self scheduleDequeueRead]; 3356 | 3357 | } 3358 | 3359 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag 3360 | { 3361 | [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:0 tag:tag]; 3362 | } 3363 | 3364 | - (void)readDataToData:(NSData *)data 3365 | withTimeout:(NSTimeInterval)timeout 3366 | buffer:(NSMutableData *)buffer 3367 | bufferOffset:(NSUInteger)offset 3368 | tag:(long)tag 3369 | { 3370 | [self readDataToData:data withTimeout:timeout buffer:buffer bufferOffset:offset maxLength:0 tag:tag]; 3371 | } 3372 | 3373 | - (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(NSUInteger)length tag:(long)tag 3374 | { 3375 | [self readDataToData:data withTimeout:timeout buffer:nil bufferOffset:0 maxLength:length tag:tag]; 3376 | } 3377 | 3378 | - (void)readDataToData:(NSData *)data 3379 | withTimeout:(NSTimeInterval)timeout 3380 | buffer:(NSMutableData *)buffer 3381 | bufferOffset:(NSUInteger)offset 3382 | maxLength:(NSUInteger)length 3383 | tag:(long)tag 3384 | { 3385 | #if DEBUG_THREAD_SAFETY 3386 | [self checkForThreadSafety]; 3387 | #endif 3388 | 3389 | if (data == nil || [data length] == 0) return; 3390 | if (offset > [buffer length]) return; 3391 | if (length > 0 && length < [data length]) return; 3392 | if (theFlags & kForbidReadsWrites) return; 3393 | 3394 | AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer 3395 | startOffset:offset 3396 | maxLength:length 3397 | timeout:timeout 3398 | readLength:0 3399 | terminator:data 3400 | tag:tag]; 3401 | [theReadQueue addObject:packet]; 3402 | [self scheduleDequeueRead]; 3403 | 3404 | } 3405 | 3406 | /** 3407 | * Puts a maybeDequeueRead on the run loop. 3408 | * An assumption here is that selectors will be performed consecutively within their priority. 3409 | **/ 3410 | - (void)scheduleDequeueRead 3411 | { 3412 | if((theFlags & kDequeueReadScheduled) == 0) 3413 | { 3414 | theFlags |= kDequeueReadScheduled; 3415 | [self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 3416 | } 3417 | } 3418 | 3419 | /** 3420 | * This method starts a new read, if needed. 3421 | * It is called when a user requests a read, 3422 | * or when a stream opens that may have requested reads sitting in the queue, etc. 3423 | **/ 3424 | - (void)maybeDequeueRead 3425 | { 3426 | // Unset the flag indicating a call to this method is scheduled 3427 | theFlags &= ~kDequeueReadScheduled; 3428 | 3429 | // If we're not currently processing a read AND we have an available read stream 3430 | if((theCurrentRead == nil) && (theReadStream != NULL)) 3431 | { 3432 | if([theReadQueue count] > 0) 3433 | { 3434 | // Dequeue the next object in the write queue 3435 | theCurrentRead = [theReadQueue objectAtIndex:0]; 3436 | [theReadQueue removeObjectAtIndex:0]; 3437 | 3438 | if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]]) 3439 | { 3440 | // Attempt to start TLS 3441 | theFlags |= kStartingReadTLS; 3442 | 3443 | // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set 3444 | [self maybeStartTLS]; 3445 | } 3446 | else 3447 | { 3448 | // Start time-out timer 3449 | if(theCurrentRead->timeout >= 0.0) 3450 | { 3451 | theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout 3452 | target:self 3453 | selector:@selector(doReadTimeout:) 3454 | userInfo:nil 3455 | repeats:NO]; 3456 | [self runLoopAddTimer:theReadTimer]; 3457 | } 3458 | 3459 | // Immediately read, if possible 3460 | [self doBytesAvailable]; 3461 | } 3462 | } 3463 | else if(theFlags & kDisconnectAfterReads) 3464 | { 3465 | if(theFlags & kDisconnectAfterWrites) 3466 | { 3467 | if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) 3468 | { 3469 | [self disconnect]; 3470 | } 3471 | } 3472 | else 3473 | { 3474 | [self disconnect]; 3475 | } 3476 | } 3477 | } 3478 | } 3479 | 3480 | /** 3481 | * Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable(). 3482 | * This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag. 3483 | **/ 3484 | - (BOOL)hasBytesAvailable 3485 | { 3486 | if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0)) 3487 | { 3488 | return YES; 3489 | } 3490 | else 3491 | { 3492 | return CFReadStreamHasBytesAvailable(theReadStream); 3493 | } 3494 | } 3495 | 3496 | /** 3497 | * Call this method in doBytesAvailable instead of CFReadStreamRead(). 3498 | * This method support pre-buffering properly. 3499 | **/ 3500 | - (CFIndex)readIntoBuffer:(void *)buffer maxLength:(NSUInteger)length 3501 | { 3502 | if([partialReadBuffer length] > 0) 3503 | { 3504 | // Determine the maximum amount of data to read 3505 | NSUInteger bytesToRead = MIN(length, [partialReadBuffer length]); 3506 | 3507 | // Copy the bytes from the partial read buffer 3508 | memcpy(buffer, [partialReadBuffer bytes], (size_t)bytesToRead); 3509 | 3510 | // Remove the copied bytes from the partial read buffer 3511 | [partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0]; 3512 | 3513 | return (CFIndex)bytesToRead; 3514 | } 3515 | else 3516 | { 3517 | // Unset the "has-bytes-available" flag 3518 | theFlags &= ~kSocketHasBytesAvailable; 3519 | 3520 | return CFReadStreamRead(theReadStream, (UInt8 *)buffer, length); 3521 | } 3522 | } 3523 | 3524 | /** 3525 | * This method is called when a new read is taken from the read queue or when new data becomes available on the stream. 3526 | **/ 3527 | - (void)doBytesAvailable 3528 | { 3529 | // If data is available on the stream, but there is no read request, then we don't need to process the data yet. 3530 | // Also, if there is a read request but no read stream setup, we can't process any data yet. 3531 | if((theCurrentRead == nil) || (theReadStream == NULL)) 3532 | { 3533 | return; 3534 | } 3535 | 3536 | // Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet) 3537 | 3538 | NSUInteger totalBytesRead = 0; 3539 | 3540 | BOOL done = NO; 3541 | BOOL socketError = NO; 3542 | BOOL maxoutError = NO; 3543 | 3544 | while(!done && !socketError && !maxoutError && [self hasBytesAvailable]) 3545 | { 3546 | BOOL didPreBuffer = NO; 3547 | BOOL didReadFromPreBuffer = NO; 3548 | 3549 | // There are 3 types of read packets: 3550 | // 3551 | // 1) Read all available data. 3552 | // 2) Read a specific length of data. 3553 | // 3) Read up to a particular terminator. 3554 | 3555 | NSUInteger bytesToRead; 3556 | 3557 | if (theCurrentRead->term != nil) 3558 | { 3559 | // Read type #3 - read up to a terminator 3560 | // 3561 | // If pre-buffering is enabled we'll read a chunk and search for the terminator. 3562 | // If the terminator is found, overflow data will be placed in the partialReadBuffer for the next read. 3563 | // 3564 | // If pre-buffering is disabled we'll be forced to read only a few bytes. 3565 | // Just enough to ensure we don't go past our term or over our max limit. 3566 | // 3567 | // If we already have data pre-buffered, we can read directly from it. 3568 | 3569 | if ([partialReadBuffer length] > 0) 3570 | { 3571 | didReadFromPreBuffer = YES; 3572 | bytesToRead = [theCurrentRead readLengthForTermWithPreBuffer:partialReadBuffer found:&done]; 3573 | } 3574 | else 3575 | { 3576 | if (theFlags & kEnablePreBuffering) 3577 | { 3578 | didPreBuffer = YES; 3579 | bytesToRead = [theCurrentRead prebufferReadLengthForTerm]; 3580 | } 3581 | else 3582 | { 3583 | bytesToRead = [theCurrentRead readLengthForTerm]; 3584 | } 3585 | } 3586 | } 3587 | else 3588 | { 3589 | // Read type #1 or #2 3590 | 3591 | bytesToRead = [theCurrentRead readLengthForNonTerm]; 3592 | } 3593 | 3594 | // Make sure we have enough room in the buffer for our read 3595 | 3596 | NSUInteger buffSize = [theCurrentRead->buffer length]; 3597 | NSUInteger buffSpace = buffSize - theCurrentRead->startOffset - theCurrentRead->bytesDone; 3598 | 3599 | if (bytesToRead > buffSpace) 3600 | { 3601 | NSUInteger buffInc = bytesToRead - buffSpace; 3602 | 3603 | [theCurrentRead->buffer increaseLengthBy:buffInc]; 3604 | } 3605 | 3606 | // Read data into packet buffer 3607 | 3608 | void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; 3609 | void *subBuffer = buffer + theCurrentRead->bytesDone; 3610 | 3611 | CFIndex result = [self readIntoBuffer:subBuffer maxLength:bytesToRead]; 3612 | 3613 | // Check results 3614 | if (result < 0) 3615 | { 3616 | socketError = YES; 3617 | } 3618 | else 3619 | { 3620 | CFIndex bytesRead = result; 3621 | 3622 | // Update total amount read for the current read 3623 | theCurrentRead->bytesDone += bytesRead; 3624 | 3625 | // Update total amount read in this method invocation 3626 | totalBytesRead += bytesRead; 3627 | 3628 | 3629 | // Is packet done? 3630 | if (theCurrentRead->readLength > 0) 3631 | { 3632 | // Read type #2 - read a specific length of data 3633 | 3634 | done = (theCurrentRead->bytesDone == theCurrentRead->readLength); 3635 | } 3636 | else if (theCurrentRead->term != nil) 3637 | { 3638 | // Read type #3 - read up to a terminator 3639 | 3640 | if (didPreBuffer) 3641 | { 3642 | // Search for the terminating sequence within the big chunk we just read. 3643 | 3644 | NSInteger overflow = [theCurrentRead searchForTermAfterPreBuffering:result]; 3645 | 3646 | if (overflow > 0) 3647 | { 3648 | // Copy excess data into partialReadBuffer 3649 | void *overflowBuffer = buffer + theCurrentRead->bytesDone - overflow; 3650 | 3651 | [partialReadBuffer appendBytes:overflowBuffer length:overflow]; 3652 | 3653 | // Update the bytesDone variable. 3654 | theCurrentRead->bytesDone -= overflow; 3655 | 3656 | // Note: The completeCurrentRead method will trim the buffer for us. 3657 | } 3658 | 3659 | done = (overflow >= 0); 3660 | } 3661 | else if (didReadFromPreBuffer) 3662 | { 3663 | // Our 'done' variable was updated via the readLengthForTermWithPreBuffer:found: method 3664 | } 3665 | else 3666 | { 3667 | // Search for the terminating sequence at the end of the buffer 3668 | 3669 | NSUInteger termlen = [theCurrentRead->term length]; 3670 | 3671 | if(theCurrentRead->bytesDone >= termlen) 3672 | { 3673 | void *bufferEnd = buffer + (theCurrentRead->bytesDone - termlen); 3674 | 3675 | const void *seq = [theCurrentRead->term bytes]; 3676 | 3677 | done = (memcmp (bufferEnd, seq, termlen) == 0); 3678 | } 3679 | } 3680 | 3681 | if(!done && theCurrentRead->maxLength > 0) 3682 | { 3683 | // We're not done and there's a set maxLength. 3684 | // Have we reached that maxLength yet? 3685 | 3686 | if(theCurrentRead->bytesDone >= theCurrentRead->maxLength) 3687 | { 3688 | maxoutError = YES; 3689 | } 3690 | } 3691 | } 3692 | else 3693 | { 3694 | // Read type #1 - read all available data 3695 | // 3696 | // We're done when: 3697 | // - we reach maxLength (if there is a max) 3698 | // - all readable is read (see below) 3699 | 3700 | if (theCurrentRead->maxLength > 0) 3701 | { 3702 | done = (theCurrentRead->bytesDone >= theCurrentRead->maxLength); 3703 | } 3704 | } 3705 | } 3706 | } 3707 | 3708 | if (theCurrentRead->readLength <= 0 && theCurrentRead->term == nil) 3709 | { 3710 | // Read type #1 - read all available data 3711 | 3712 | if (theCurrentRead->bytesDone > 0) 3713 | { 3714 | // Ran out of bytes, so the "read-all-available-data" type packet is done 3715 | done = YES; 3716 | } 3717 | } 3718 | 3719 | if (done) 3720 | { 3721 | [self completeCurrentRead]; 3722 | if (!socketError) [self scheduleDequeueRead]; 3723 | } 3724 | else if (totalBytesRead > 0) 3725 | { 3726 | // We're not done with the readToLength or readToData yet, but we have read in some bytes 3727 | if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) 3728 | { 3729 | [theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; 3730 | } 3731 | } 3732 | 3733 | if(socketError) 3734 | { 3735 | CFStreamError err = CFReadStreamGetError(theReadStream); 3736 | [self closeWithError:[self errorFromCFStreamError:err]]; 3737 | return; 3738 | } 3739 | 3740 | if(maxoutError) 3741 | { 3742 | [self closeWithError:[self getReadMaxedOutError]]; 3743 | return; 3744 | } 3745 | } 3746 | 3747 | // Ends current read and calls delegate. 3748 | - (void)completeCurrentRead 3749 | { 3750 | NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read."); 3751 | 3752 | NSData *result; 3753 | 3754 | if (theCurrentRead->bufferOwner) 3755 | { 3756 | // We created the buffer on behalf of the user. 3757 | // Trim our buffer to be the proper size. 3758 | [theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; 3759 | 3760 | result = theCurrentRead->buffer; 3761 | } 3762 | else 3763 | { 3764 | // We did NOT create the buffer. 3765 | // The buffer is owned by the caller. 3766 | // Only trim the buffer if we had to increase its size. 3767 | 3768 | if ([theCurrentRead->buffer length] > theCurrentRead->originalBufferLength) 3769 | { 3770 | NSUInteger readSize = theCurrentRead->startOffset + theCurrentRead->bytesDone; 3771 | NSUInteger origSize = theCurrentRead->originalBufferLength; 3772 | 3773 | NSUInteger buffSize = MAX(readSize, origSize); 3774 | 3775 | [theCurrentRead->buffer setLength:buffSize]; 3776 | } 3777 | 3778 | void *buffer = [theCurrentRead->buffer mutableBytes] + theCurrentRead->startOffset; 3779 | 3780 | result = [NSData dataWithBytesNoCopy:buffer length:theCurrentRead->bytesDone freeWhenDone:NO]; 3781 | } 3782 | 3783 | if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) 3784 | { 3785 | [theDelegate onSocket:self didReadData:result withTag:theCurrentRead->tag]; 3786 | } 3787 | 3788 | // Caller may have disconnected in the above delegate method 3789 | if (theCurrentRead != nil) 3790 | { 3791 | [self endCurrentRead]; 3792 | } 3793 | } 3794 | 3795 | // Ends current read. 3796 | - (void)endCurrentRead 3797 | { 3798 | NSAssert(theCurrentRead, @"Trying to end current read when there is no current read."); 3799 | 3800 | [theReadTimer invalidate]; 3801 | theReadTimer = nil; 3802 | 3803 | theCurrentRead = nil; 3804 | } 3805 | 3806 | - (void)doReadTimeout:(NSTimer *)timer 3807 | { 3808 | #pragma unused(timer) 3809 | 3810 | NSTimeInterval timeoutExtension = 0.0; 3811 | 3812 | if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) 3813 | { 3814 | timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag 3815 | elapsed:theCurrentRead->timeout 3816 | bytesDone:theCurrentRead->bytesDone]; 3817 | } 3818 | 3819 | if(timeoutExtension > 0.0) 3820 | { 3821 | theCurrentRead->timeout += timeoutExtension; 3822 | 3823 | theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension 3824 | target:self 3825 | selector:@selector(doReadTimeout:) 3826 | userInfo:nil 3827 | repeats:NO]; 3828 | [self runLoopAddTimer:theReadTimer]; 3829 | } 3830 | else 3831 | { 3832 | // Do not call endCurrentRead here. 3833 | // We must allow the delegate access to any partial read in the unreadData method. 3834 | 3835 | [self closeWithError:[self getReadTimeoutError]]; 3836 | } 3837 | } 3838 | 3839 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3840 | #pragma mark Writing 3841 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 3842 | 3843 | - (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag 3844 | { 3845 | #if DEBUG_THREAD_SAFETY 3846 | [self checkForThreadSafety]; 3847 | #endif 3848 | 3849 | if (data == nil || [data length] == 0) return; 3850 | if (theFlags & kForbidReadsWrites) return; 3851 | 3852 | AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; 3853 | 3854 | [theWriteQueue addObject:packet]; 3855 | [self scheduleDequeueWrite]; 3856 | 3857 | } 3858 | 3859 | - (void)scheduleDequeueWrite 3860 | { 3861 | if((theFlags & kDequeueWriteScheduled) == 0) 3862 | { 3863 | theFlags |= kDequeueWriteScheduled; 3864 | [self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; 3865 | } 3866 | } 3867 | 3868 | /** 3869 | * Conditionally starts a new write. 3870 | * 3871 | * IF there is not another write in process 3872 | * AND there is a write queued 3873 | * AND we have a write stream available 3874 | * 3875 | * This method also handles auto-disconnect post read/write completion. 3876 | **/ 3877 | - (void)maybeDequeueWrite 3878 | { 3879 | // Unset the flag indicating a call to this method is scheduled 3880 | theFlags &= ~kDequeueWriteScheduled; 3881 | 3882 | // If we're not currently processing a write AND we have an available write stream 3883 | if((theCurrentWrite == nil) && (theWriteStream != NULL)) 3884 | { 3885 | if([theWriteQueue count] > 0) 3886 | { 3887 | // Dequeue the next object in the write queue 3888 | theCurrentWrite = [theWriteQueue objectAtIndex:0]; 3889 | [theWriteQueue removeObjectAtIndex:0]; 3890 | 3891 | if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) 3892 | { 3893 | // Attempt to start TLS 3894 | theFlags |= kStartingWriteTLS; 3895 | 3896 | // This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are set 3897 | [self maybeStartTLS]; 3898 | } 3899 | else 3900 | { 3901 | // Start time-out timer 3902 | if(theCurrentWrite->timeout >= 0.0) 3903 | { 3904 | theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout 3905 | target:self 3906 | selector:@selector(doWriteTimeout:) 3907 | userInfo:nil 3908 | repeats:NO]; 3909 | [self runLoopAddTimer:theWriteTimer]; 3910 | } 3911 | 3912 | // Immediately write, if possible 3913 | [self doSendBytes]; 3914 | } 3915 | } 3916 | else if(theFlags & kDisconnectAfterWrites) 3917 | { 3918 | if(theFlags & kDisconnectAfterReads) 3919 | { 3920 | if(([theReadQueue count] == 0) && (theCurrentRead == nil)) 3921 | { 3922 | [self disconnect]; 3923 | } 3924 | } 3925 | else 3926 | { 3927 | [self disconnect]; 3928 | } 3929 | } 3930 | } 3931 | } 3932 | 3933 | /** 3934 | * Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes(). 3935 | * This method supports the kSocketCanAcceptBytes flag. 3936 | **/ 3937 | - (BOOL)canAcceptBytes 3938 | { 3939 | if (theFlags & kSocketCanAcceptBytes) 3940 | { 3941 | return YES; 3942 | } 3943 | else 3944 | { 3945 | return CFWriteStreamCanAcceptBytes(theWriteStream); 3946 | } 3947 | } 3948 | 3949 | - (void)doSendBytes 3950 | { 3951 | if ((theCurrentWrite == nil) || (theWriteStream == NULL)) 3952 | { 3953 | return; 3954 | } 3955 | 3956 | // Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet) 3957 | 3958 | NSUInteger totalBytesWritten = 0; 3959 | 3960 | BOOL done = NO; 3961 | BOOL error = NO; 3962 | 3963 | while (!done && !error && [self canAcceptBytes]) 3964 | { 3965 | // Figure out what to write 3966 | NSUInteger bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; 3967 | NSUInteger bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; 3968 | 3969 | UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); 3970 | 3971 | // Write 3972 | CFIndex result = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite); 3973 | 3974 | // Unset the "can accept bytes" flag 3975 | theFlags &= ~kSocketCanAcceptBytes; 3976 | 3977 | // Check results 3978 | if (result < 0) 3979 | { 3980 | error = YES; 3981 | } 3982 | else 3983 | { 3984 | CFIndex bytesWritten = result; 3985 | 3986 | // Update total amount read for the current write 3987 | theCurrentWrite->bytesDone += bytesWritten; 3988 | 3989 | // Update total amount written in this method invocation 3990 | totalBytesWritten += bytesWritten; 3991 | 3992 | // Is packet done? 3993 | done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); 3994 | } 3995 | } 3996 | 3997 | if(done) 3998 | { 3999 | [self completeCurrentWrite]; 4000 | [self scheduleDequeueWrite]; 4001 | } 4002 | else if(error) 4003 | { 4004 | CFStreamError err = CFWriteStreamGetError(theWriteStream); 4005 | [self closeWithError:[self errorFromCFStreamError:err]]; 4006 | return; 4007 | } 4008 | else if (totalBytesWritten > 0) 4009 | { 4010 | // We're not done with the entire write, but we have written some bytes 4011 | if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)]) 4012 | { 4013 | [theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag]; 4014 | } 4015 | } 4016 | } 4017 | 4018 | // Ends current write and calls delegate. 4019 | - (void)completeCurrentWrite 4020 | { 4021 | NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); 4022 | 4023 | if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) 4024 | { 4025 | [theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; 4026 | } 4027 | 4028 | if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. 4029 | } 4030 | 4031 | // Ends current write. 4032 | - (void)endCurrentWrite 4033 | { 4034 | NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); 4035 | 4036 | [theWriteTimer invalidate]; 4037 | theWriteTimer = nil; 4038 | 4039 | theCurrentWrite = nil; 4040 | } 4041 | 4042 | - (void)doWriteTimeout:(NSTimer *)timer 4043 | { 4044 | #pragma unused(timer) 4045 | 4046 | NSTimeInterval timeoutExtension = 0.0; 4047 | 4048 | if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) 4049 | { 4050 | timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag 4051 | elapsed:theCurrentWrite->timeout 4052 | bytesDone:theCurrentWrite->bytesDone]; 4053 | } 4054 | 4055 | if(timeoutExtension > 0.0) 4056 | { 4057 | theCurrentWrite->timeout += timeoutExtension; 4058 | 4059 | theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension 4060 | target:self 4061 | selector:@selector(doWriteTimeout:) 4062 | userInfo:nil 4063 | repeats:NO]; 4064 | [self runLoopAddTimer:theWriteTimer]; 4065 | } 4066 | else 4067 | { 4068 | [self closeWithError:[self getWriteTimeoutError]]; 4069 | } 4070 | } 4071 | 4072 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4073 | #pragma mark Security 4074 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4075 | 4076 | - (void)startTLS:(NSDictionary *)tlsSettings 4077 | { 4078 | #if DEBUG_THREAD_SAFETY 4079 | [self checkForThreadSafety]; 4080 | #endif 4081 | 4082 | if(tlsSettings == nil) 4083 | { 4084 | // Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, 4085 | // but causes problems if we later try to fetch the remote host's certificate. 4086 | // 4087 | // To be exact, it causes the following to return NULL instead of the normal result: 4088 | // CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) 4089 | // 4090 | // So we use an empty dictionary instead, which works perfectly. 4091 | 4092 | tlsSettings = [NSDictionary dictionary]; 4093 | } 4094 | 4095 | AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; 4096 | 4097 | [theReadQueue addObject:packet]; 4098 | [self scheduleDequeueRead]; 4099 | 4100 | [theWriteQueue addObject:packet]; 4101 | [self scheduleDequeueWrite]; 4102 | 4103 | } 4104 | 4105 | - (void)maybeStartTLS 4106 | { 4107 | // We can't start TLS until: 4108 | // - All queued reads prior to the user calling StartTLS are complete 4109 | // - All queued writes prior to the user calling StartTLS are complete 4110 | // 4111 | // We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set 4112 | 4113 | if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) 4114 | { 4115 | AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead; 4116 | 4117 | BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings, 4118 | (__bridge CFDictionaryRef)tlsPacket->tlsSettings); 4119 | BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings, 4120 | (__bridge CFDictionaryRef)tlsPacket->tlsSettings); 4121 | 4122 | if(!didStartOnReadStream || !didStartOnWriteStream) 4123 | { 4124 | [self closeWithError:[self getSocketError]]; 4125 | } 4126 | } 4127 | } 4128 | 4129 | - (void)onTLSHandshakeSuccessful 4130 | { 4131 | if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) 4132 | { 4133 | theFlags &= ~kStartingReadTLS; 4134 | theFlags &= ~kStartingWriteTLS; 4135 | 4136 | if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)]) 4137 | { 4138 | [theDelegate onSocketDidSecure:self]; 4139 | } 4140 | 4141 | [self endCurrentRead]; 4142 | [self endCurrentWrite]; 4143 | 4144 | [self scheduleDequeueRead]; 4145 | [self scheduleDequeueWrite]; 4146 | } 4147 | } 4148 | 4149 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4150 | #pragma mark CF Callbacks 4151 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4152 | 4153 | - (void)doCFSocketCallback:(CFSocketCallBackType)type 4154 | forSocket:(CFSocketRef)sock 4155 | withAddress:(NSData *)address 4156 | withData:(const void *)pData 4157 | { 4158 | #pragma unused(address) 4159 | 4160 | NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); 4161 | 4162 | switch (type) 4163 | { 4164 | case kCFSocketConnectCallBack: 4165 | // The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed. 4166 | if(pData) 4167 | [self doSocketOpen:sock withCFSocketError:kCFSocketError]; 4168 | else 4169 | [self doSocketOpen:sock withCFSocketError:kCFSocketSuccess]; 4170 | break; 4171 | case kCFSocketAcceptCallBack: 4172 | [self doAcceptFromSocket:sock withNewNativeSocket:*((CFSocketNativeHandle *)pData)]; 4173 | break; 4174 | default: 4175 | NSLog(@"AsyncSocket %p received unexpected CFSocketCallBackType %i", self, (int)type); 4176 | break; 4177 | } 4178 | } 4179 | 4180 | - (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream 4181 | { 4182 | #pragma unused(stream) 4183 | 4184 | NSParameterAssert(theReadStream != NULL); 4185 | 4186 | CFStreamError err; 4187 | switch (type) 4188 | { 4189 | case kCFStreamEventOpenCompleted: 4190 | theFlags |= kDidCompleteOpenForRead; 4191 | [self doStreamOpen]; 4192 | break; 4193 | case kCFStreamEventHasBytesAvailable: 4194 | if(theFlags & kStartingReadTLS) { 4195 | [self onTLSHandshakeSuccessful]; 4196 | } 4197 | else { 4198 | theFlags |= kSocketHasBytesAvailable; 4199 | [self doBytesAvailable]; 4200 | } 4201 | break; 4202 | case kCFStreamEventErrorOccurred: 4203 | case kCFStreamEventEndEncountered: 4204 | err = CFReadStreamGetError (theReadStream); 4205 | [self closeWithError: [self errorFromCFStreamError:err]]; 4206 | break; 4207 | default: 4208 | NSLog(@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %i", self, (int)type); 4209 | } 4210 | } 4211 | 4212 | - (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream 4213 | { 4214 | #pragma unused(stream) 4215 | 4216 | NSParameterAssert(theWriteStream != NULL); 4217 | 4218 | CFStreamError err; 4219 | switch (type) 4220 | { 4221 | case kCFStreamEventOpenCompleted: 4222 | theFlags |= kDidCompleteOpenForWrite; 4223 | [self doStreamOpen]; 4224 | break; 4225 | case kCFStreamEventCanAcceptBytes: 4226 | if(theFlags & kStartingWriteTLS) { 4227 | [self onTLSHandshakeSuccessful]; 4228 | } 4229 | else { 4230 | theFlags |= kSocketCanAcceptBytes; 4231 | [self doSendBytes]; 4232 | } 4233 | break; 4234 | case kCFStreamEventErrorOccurred: 4235 | case kCFStreamEventEndEncountered: 4236 | err = CFWriteStreamGetError (theWriteStream); 4237 | [self closeWithError: [self errorFromCFStreamError:err]]; 4238 | break; 4239 | default: 4240 | NSLog(@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %i", self, (int)type); 4241 | } 4242 | } 4243 | 4244 | /** 4245 | * This is the callback we setup for CFSocket. 4246 | * This method does nothing but forward the call to it's Objective-C counterpart 4247 | **/ 4248 | static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef inAddress, const void *pData, void *pInfo) 4249 | { 4250 | @autoreleasepool { 4251 | 4252 | AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; 4253 | NSData *address = [(__bridge NSData *)inAddress copy]; 4254 | 4255 | [theSocket doCFSocketCallback:type forSocket:sref withAddress:address withData:pData]; 4256 | 4257 | } 4258 | } 4259 | 4260 | /** 4261 | * This is the callback we setup for CFReadStream. 4262 | * This method does nothing but forward the call to it's Objective-C counterpart 4263 | **/ 4264 | static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) 4265 | { 4266 | @autoreleasepool { 4267 | 4268 | AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; 4269 | [theSocket doCFReadStreamCallback:type forStream:stream]; 4270 | 4271 | } 4272 | } 4273 | 4274 | /** 4275 | * This is the callback we setup for CFWriteStream. 4276 | * This method does nothing but forward the call to it's Objective-C counterpart 4277 | **/ 4278 | static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) 4279 | { 4280 | @autoreleasepool { 4281 | 4282 | AsyncSocket *theSocket = (__bridge AsyncSocket *)pInfo; 4283 | [theSocket doCFWriteStreamCallback:type forStream:stream]; 4284 | 4285 | } 4286 | } 4287 | 4288 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4289 | #pragma mark Class Methods 4290 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 4291 | 4292 | // Return line separators. 4293 | + (NSData *)CRLFData 4294 | { 4295 | return [NSData dataWithBytes:"\x0D\x0A" length:2]; 4296 | } 4297 | 4298 | + (NSData *)CRData 4299 | { 4300 | return [NSData dataWithBytes:"\x0D" length:1]; 4301 | } 4302 | 4303 | + (NSData *)LFData 4304 | { 4305 | return [NSData dataWithBytes:"\x0A" length:1]; 4306 | } 4307 | 4308 | + (NSData *)ZeroData 4309 | { 4310 | return [NSData dataWithBytes:"" length:1]; 4311 | } 4312 | 4313 | @end 4314 | -------------------------------------------------------------------------------- /AsyncSocketDemoTests/AsyncSocketDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // AsyncSocketDemoTests.m 3 | // AsyncSocketDemoTests 4 | // 5 | // Created by ligang on 15/4/3. 6 | // Copyright (c) 2015年 ligang. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface AsyncSocketDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation AsyncSocketDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /AsyncSocketDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | ligang.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AsyncSocketDemo 2 | 3 | 4 | 用socket可以实现像QQ那样发送即时消息的功能。客户端和服务端需要建立长连接,在长连接的情况下,发送消息。客户端可以发送心跳包来检测长连接。 5 | 6 | 在iOS开发中使用socket,一般都是用第三方库AsyncSocket,不得不承认这个库确实很强大。下载地址[CocoaAsyncSocket](https://github.com/robbiehanson/CocoaAsyncSocket.git)。 7 | 8 | 使用AsyncSocket的时候可以做一层封装,根据需求提供几个接口出来。比如:连接、断开连接、发送消息等等。还有接受消息,接受到的消息可以通过通知、代理、block等传出去。 9 | 10 | 11 | 简单介绍一下对AsyncSocket使用.一般来说,一个用户只需要建立一个socket长连接,所以可以用单例类方便使用。 12 | 13 | ###定义单列类:LGSocketServe 14 | 15 | LGSocketServe.h 16 | 17 | // 18 | // LGSocketServe.h 19 | // AsyncSocketDemo 20 | // 21 | // Created by ligang on 15/4/3. 22 | // Copyright (c) 2015年 ligang. All rights reserved. 23 | // 24 | 25 | #import 26 | #import "AsyncSocket.h" 27 | 28 | @interface LGSocketServe : NSObject 29 | 30 | + (LGSocketServe *)sharedSocketServe; 31 | 32 | 33 | @end 34 | 35 | LGSocketServe.m 36 | 37 | // 38 | // LGSocketServe.m 39 | // AsyncSocketDemo 40 | // 41 | // Created by ligang on 15/4/3. 42 | // Copyright (c) 2015年 ligang. All rights reserved. 43 | // 44 | 45 | #import "LGSocketServe.h" 46 | 47 | @implementation LGSocketServe 48 | 49 | 50 | static LGSocketServe *socketServe = nil; 51 | 52 | #pragma mark public static methods 53 | 54 | 55 | + (LGSocketServe *)sharedSocketServe { 56 | @synchronized(self) { 57 | if(socketServe == nil) { 58 | socketServe = [[[self class] alloc] init]; 59 | } 60 | } 61 | return socketServe; 62 | } 63 | 64 | 65 | +(id)allocWithZone:(NSZone *)zone 66 | { 67 | @synchronized(self) 68 | { 69 | if (socketServe == nil) 70 | { 71 | socketServe = [super allocWithZone:zone]; 72 | return socketServe; 73 | } 74 | } 75 | return nil; 76 | } 77 | 78 | 79 | @end 80 | 81 | 82 | ###建立socket长连接 83 | 84 | LGSocketServe.h 85 | 86 | @property (nonatomic, strong) AsyncSocket *socket; // socket 87 | 88 | // socket连接 89 | - (void)startConnectSocket; 90 | 91 | LGSocketServe.m 92 | 93 | //自己设定 94 | #define HOST @"192.168.0.1" 95 | #define PORT 8080 96 | 97 | //设置连接超时 98 | #define TIME_OUT 20 99 | 100 | - (void)startConnectSocket 101 | { 102 | self.socket = [[AsyncSocket alloc] initWithDelegate:self]; 103 | [self.socket setRunLoopModes:[NSArray arrayWithObject:NSRunLoopCommonModes]]; 104 | if ( ![self SocketOpen:HOST port:PORT] ) 105 | { 106 | 107 | } 108 | 109 | } 110 | 111 | - (NSInteger)SocketOpen:(NSString*)addr port:(NSInteger)port 112 | { 113 | 114 | if (![self.socket isConnected]) 115 | { 116 | NSError *error = nil; 117 | [self.socket connectToHost:addr onPort:port withTimeout:TIME_OUT error:&error]; 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | 宏定义一下HOST、PORT、TIME_OUT,实现startConnectSocket方法。这个时候要设置一下AsyncSocket的代理AsyncSocketDelegate。当长连接成功之后会调用: 124 | 125 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port 126 | { 127 | //这是异步返回的连接成功, 128 | NSLog(@"didConnectToHost"); 129 | } 130 | 131 | 132 | 133 | ###心跳 134 | 135 | LGSocketServe.h 136 | 137 | @property (nonatomic, retain) NSTimer *heartTimer; // 心跳计时器 138 | 139 | LGSocketServe.m 140 | 141 | - (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port 142 | { 143 | //这是异步返回的连接成功, 144 | NSLog(@"didConnectToHost"); 145 | 146 | //通过定时器不断发送消息,来检测长连接 147 | self.heartTimer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(checkLongConnectByServe) userInfo:nil repeats:YES]; 148 | [self.heartTimer fire]; 149 | } 150 | 151 | // 心跳连接 152 | -(void)checkLongConnectByServe{ 153 | 154 | // 向服务器发送固定可是的消息,来检测长连接 155 | NSString *longConnect = @"connect is here"; 156 | NSData *data = [longConnect dataUsingEncoding:NSUTF8StringEncoding]; 157 | [self.socket writeData:data withTimeout:1 tag:1]; 158 | } 159 | 160 | 在连接成功的回调方法里,启动定时器,每隔2秒向服务器发送固定的消息来检测长连接。(这个根据服务器的需要就可以了) 161 | 162 | ###断开连接 163 | 164 | 1,用户手动断开连接 165 | 166 | LGSocketServe.h 167 | 168 | // 断开socket连接 169 | -(void)cutOffSocket; 170 | 171 | LGSocketServe.m 172 | 173 | -(void)cutOffSocket 174 | { 175 | self.socket.userData = SocketOfflineByUser; 176 | [self.socket disconnect]; 177 | } 178 | 179 | cutOffSocket是用户断开连接之后,不在尝试重新连接。 180 | 181 | 2,wifi断开,socket断开连接 182 | 183 | LGSocketServe.m 184 | 185 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err 186 | { 187 | 188 | NSLog(@" willDisconnectWithError %ld err = %@",sock.userData,[err description]); 189 | if (err.code == 57) { 190 | self.socket.userData = SocketOfflineByWifiCut; 191 | } 192 | 193 | } 194 | 195 | wifi断开之后,会回调onSocket:willDisconnectWithError:方法,err.code == 57,这个时候设置self.socket.userData = SocketOfflineByWifiCut。 196 | 197 | ###重新连接 198 | 199 | socket断开之后会回调: 200 | 201 | LGSocketServe.m 202 | 203 | - (void)onSocketDidDisconnect:(AsyncSocket *)sock 204 | { 205 | 206 | NSLog(@"7878 sorry the connect is failure %ld",sock.userData); 207 | 208 | if (sock.userData == SocketOfflineByServer) { 209 | // 服务器掉线,重连 210 | [self startConnectSocket]; 211 | } 212 | else if (sock.userData == SocketOfflineByUser) { 213 | 214 | // 如果由用户断开,不进行重连 215 | return; 216 | }else if (sock.userData == SocketOfflineByWifiCut) { 217 | 218 | // wifi断开,不进行重连 219 | return; 220 | } 221 | 222 | } 223 | 224 | 在onSocketDidDisconnect回调方法里面,会根据self.socket.userData来判断是否需要重新连接。 225 | 226 | ###发送消息 227 | 228 | LGSocketServe.h 229 | 230 | // 发送消息 231 | - (void)sendMessage:(id)message; 232 | 233 | LGSocketServe.m 234 | 235 | //设置写入超时 -1 表示不会使用超时 236 | #define WRITE_TIME_OUT -1 237 | 238 | - (void)sendMessage:(id)message 239 | { 240 | //像服务器发送数据 241 | NSData *cmdData = [message dataUsingEncoding:NSUTF8StringEncoding]; 242 | [self.socket writeData:cmdData withTimeout:WRITE_TIME_OUT tag:1]; 243 | } 244 | 245 | //发送消息成功之后回调 246 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag 247 | { 248 | 249 | } 250 | 251 | 发送消息成功之后会调用onSocket:didWriteDataWithTag:,在这个方法里可以进行读取消息。 252 | 253 | ###接受消息 254 | 255 | LGSocketServe.m 256 | 257 | //设置读取超时 -1 表示不会使用超时 258 | #define READ_TIME_OUT -1 259 | 260 | #define MAX_BUFFER 1024 261 | 262 | //发送消息成功之后回调 263 | - (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag 264 | { 265 | //读取消息 266 | [self.socket readDataWithTimeout:-1 buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0]; 267 | } 268 | 269 | //接受消息成功之后回调 270 | - (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 271 | { 272 | //服务端返回消息数据量比较大时,可能分多次返回。所以在读取消息的时候,设置MAX_BUFFER表示每次最多读取多少,当data.length < MAX_BUFFER我们认为有可能是接受完一个完整的消息,然后才解析 273 | if( data.length < MAX_BUFFER ) 274 | { 275 | //收到结果解析... 276 | NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil]; 277 | NSLog(@"%@",dic); 278 | //解析出来的消息,可以通过通知、代理、block等传出去 279 | 280 | } 281 | 282 | 283 | [self.socket readDataWithTimeout:READ_TIME_OUT buffer:nil bufferOffset:0 maxLength:MAX_BUFFER tag:0]; 284 | 285 | 286 | 接受消息后去解析,然后可以通过通知、代理、block等传出去。在onSocket:didReadData:withTag:回调方法里面需要不断读取消息,因为数据量比较大的话,服务器会分多次返回。所以我们需要定义一个MAX_BUFFER的宏,表示每次最多读取多少。当data.length < MAX_BUFFER我们认为有可能是接受完一个完整的消息,然后才解析 287 | 。 288 | 289 | ###出错处理 290 | 291 | LGSocketServe.m 292 | 293 | - (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err 294 | { 295 | NSData * unreadData = [sock unreadData]; // ** This gets the current buffer 296 | if(unreadData.length > 0) { 297 | [self onSocket:sock didReadData:unreadData withTag:0]; // ** Return as much data that could be collected 298 | } else { 299 | 300 | NSLog(@" willDisconnectWithError %ld err = %@",sock.userData,[err description]); 301 | if (err.code == 57) { 302 | self.socket.userData = SocketOfflineByWifiCut; 303 | } 304 | } 305 | 306 | } 307 | 308 | socket出错会回调onSocket:willDisconnectWithError:方法,可以通过unreadData来读取未来得及读取的buffer。 309 | 310 | ###使用 311 | 312 | 导入#import "LGSocketServe.h" 313 | 314 | LGSocketServe *socketServe = [LGSocketServe sharedSocketServe]; 315 | //socket连接前先断开连接以免之前socket连接没有断开导致闪退 316 | [socketServe cutOffSocket]; 317 | socketServe.socket.userData = SocketOfflineByServer; 318 | [socketServe startConnectSocket]; 319 | 320 | //发送消息 @"hello world"只是举个列子,具体根据服务端的消息格式 321 | [socketServe sendMessage:@"hello world"]; 322 | 323 | 以上是AsyncSocket的简单使用,在实际开发过程中依然会碰到很多问题,欢迎加我的微信公众号iOS开发:iOSDevTip,一起讨论AsyncSocket中遇到的问题。 324 | 325 | --------------------------------------------------------------------------------